如何使用JavaScript创建具有多个可选参数的搜索?

时间:2019-07-09 00:12:52

标签: javascript reactjs search filter

我目前有“作品”,但是每个参数都取决于最后一个。我的目标是允许用户使用任何数量的搜索字段来过滤帖子,但似乎无法将我的头全围绕如何实际执行。

搜索字段的代码:

import React from "react";
import { Input, DropDown } from "../Form";
import "./index.css";

function Sidebar(props) {
  return (
    <div className="sidebar-container">
      <p>Search Posts: {props.carMake}</p>
      <div className="field-wrap">
        <Input
          value={props.carMake}
          onChange={props.handleInputChange}
          name="carMake"
          type="text"
          placeholder="Manufacturer"
        />
      </div>
      <div className="field-wrap">
        <Input
          value={props.carModel}
          onChange={props.handleInputChange}
          disabled={!props.carMake}
          name="carModel"
          type="text"
          placeholder="Model"
        />
      </div>
      <div className="field-wrap">
        <Input
          disabled={!props.carModel || !props.carMake}
          value={props.carYear}
          onChange={props.handleInputChange}
          name="carYear"
          type="text"
          placeholder="Year"
        />
      </div>
      <div className="field-wrap">
        <DropDown
          //disabled={!props.carModel || !props.carMake || !props.carYear}
          value={props.category}
          onChange={props.handleInputChange}
          name="category"
          type="text"
          id="category"
        >
          <option>Select a category...</option>
          <option>Brakes</option>
          <option>Drivetrain</option>
          <option>Engine</option>
          <option>Exhaust</option>
          <option>Exterior</option>
          <option>Intake</option>
          <option>Interior</option>
          <option>Lights</option>
          <option>Suspension</option>
          <option>Wheels & Tires</option>
        </DropDown>
      </div>
    </div>
  );
}

export default Sidebar;

这是父组件的代码(实际过滤数据的地方):

import React, { Component } from 'react';
import Sidebar from '../../components/Sidebar';
import API from '../../utils/API';
import PostContainer from '../../components/PostContainer';
import { withRouter } from 'react-router';
import axios from 'axios';
import './index.css';

class Posts extends Component {
  constructor(props) {
    super(props);
    this.state = {
      posts: [],
      carMake: '',
      carModel: '',
      carYear: '',
      category: 'Select A Category...'
    };
    this.signal = axios.CancelToken.source();
  }

  componentDidMount() {
    API.getAllPosts({
      cancelToken: this.signal.token
    })
      .then(resp => {
        this.setState({
          posts: resp.data
        });
      })
      .catch(function(error) {
        if (axios.isCancel(error)) {
          console.log('Error: ', error.message);
        } else {
          console.log(error);
        }
      });
  }

  componentWillUnmount() {
    this.signal.cancel('Api is being canceled');
  }

  handleInputChange = event => {
    const { name, value } = event.target;
    this.setState({
      [name]: value
    });
  };

  handleFormSubmit = event => {
    event.preventDefault();
    console.log('Form Submitted');
  };

  render() {
    const { carMake, carModel, carYear, category, posts } = this.state;

    const filterMake = posts.filter(
      post => post.carMake.toLowerCase().indexOf(carMake.toLowerCase()) !== -1
    );
    const filterModel = posts.filter(
      post => post.carModel.toLowerCase().indexOf(carModel.toLowerCase()) !== -1
    );
    const filterYear = posts.filter(
      post => post.carYear.toString().indexOf(carYear.toString()) !== -1
    );
    const filterCategory = posts.filter(
      post => post.category.toLowerCase().indexOf(category.toLowerCase()) !== -1
    );

    return (
      <div className='container-fluid'>
        <div className='row'>
          <div className='col-xl-2 col-lg-3 col-md-4 col-sm-12'>
            <Sidebar
              carMake={carMake}
              carModel={carModel}
              carYear={carYear}
              category={category}
              handleInputChange={this.handleInputChange}
              handleFormSubmit={event => {
                event.preventDefault();
                this.handleFormSubmit(event);
              }}
            />
          </div>
          <div className='col-xl-8 col-lg-7 col-md-6 col-sm-12 offset-md-1'>
            {carMake && carModel && carYear && category
              ? filterCategory.map(post => (
                  <PostContainer post={post} key={post.id} />
                ))
              : carMake && carModel && carYear
              ? filterYear.map(post => (
                  <PostContainer post={post} key={post.id} />
                ))
              : carMake && carModel
              ? filterModel.map(post => (
                  <PostContainer post={post} key={post.id} />
                ))
              : carMake
              ? filterMake.map(post => (
                  <PostContainer post={post} key={post.id} />
                ))
              : posts.map(post => <PostContainer post={post} key={post.id} />)}
          </div>
        </div>
      </div>
    );
  }
}

export default withRouter(Posts);

从API返回的数据采用对象数组的形式,如下所示:

[{

"id":4,
"title":"1995 Toyota Supra",
"desc":"asdf",
"itemImg":"https://i.imgur.com/zsd7N8M.jpg",
"price":32546,
"carYear":1995,
"carMake":"Toyota",
"carModel":"Supra",
"location":"Phoenix, AZ",
"category":"Exhaust",
"createdAt":"2019-07-09T00:00:46.000Z",
"updatedAt":"2019-07-09T00:00:46.000Z",
"UserId":1

},{

"id":3,
"title":"Trash",
"desc":"sdfasdf",
"itemImg":"https://i.imgur.com/rcyWOQG.jpg",
"price":2345,
"carYear":2009,
"carMake":"Yes",
"carModel":"Ayylmao",
"location":"asdf",
"category":"Drivetrain",
"createdAt":"2019-07-08T23:33:04.000Z",
"updatedAt":"2019-07-08T23:33:04.000Z",
"UserId":1

}]

从上面可以看出,我试图仅注释掉下拉列表的“ Disabled”属性,但是这导致它完全停止充当过滤器,并且无论选择如何都返回所有结果。这是由于我的三元运算符检查每个过滤器而造成的。有没有更好的方法可以做到这一点?

6 个答案:

答案 0 :(得分:4)

即使@Nina Lisitsinskaya的回答是正确的,我也不会有大量的if列表,而只是通过过滤器串联来完成。

这样,添加另一种过滤方法更加容易且易于阅读。但是,解决方案是相似的。

render() {
    const { carMake = '', carModel = '', carYear = '', category = '', posts } = this.state;

    let filtered = [...posts];

        filtered = filtered
            .filter(post => post.carMake.toLowerCase().indexOf(carMake.toLowerCase()) !== -1)
            .filter(post => post.carModel.toLowerCase().indexOf(carModel.toLowerCase()) !== -1)
            .filter(post => post.carYear.toString().indexOf(carYear.toString()) !== -1)
            .filter(post => post.category.toLowerCase().indexOf(category.toLowerCase()) !== -1)

    ...
}

当然,稍后您要在JSX表达式中与此类似地使用filtered,否则就没有任何显示。

  ...

  <div className='col-xl-8 col-lg-7 col-md-6 col-sm-12 offset-md-1'>
    {filtered.map(post => <PostContainer post={post} key={post.id} />)}
  </div>

答案 1 :(得分:0)

根本不需要在JSX中使用可怕的巨大三元运算符。首先,您可以使用每个过滤器依次过滤集合:

render() {
  const { carMake, carModel, carYear, category, posts } = this.state;

  let filtered = [...posts];

  if (carMake) {
    filtered = filtered.filter(post => post.carMake.toLowerCase().indexOf(carMake.toLowerCase()) !== -1);
  }

  if (carModel) {
    filtered = filtered.filter(post => post.carModel.toLowerCase().indexOf(carModel.toLowerCase()) !== -1);
  }

  if (carYear) {
    filtered = filtered.filter(post => post.carYear.toString().indexOf(carYear.toString()) !== -1);
  }

  if (category) {
    filtered = filtered.filter(post => post.category.toLowerCase().indexOf(category.toLowerCase()) !== -1);
  }

  ...

然后,您只需在JSX表达式中使用filtered

  ...

  <div className='col-xl-8 col-lg-7 col-md-6 col-sm-12 offset-md-1'>
    {filtered.map(post => <PostContainer post={post} key={post.id} />)}
  </div>

答案 2 :(得分:0)

永远不要在render方法中进行此类计算-它应与干净计算的state/props一起使用。基本上应该在backend上进行过滤,但是如果要在frontend上进行过滤,则应将过滤逻辑移至服务方法,如下所示:

function getPosts({ cancelToken, filter }) {
    // first fetch your posts
    // const posts = ...

    const { carMake, carModel, carYear, category } = filter;

    let filtered = [];
    for (let i = 0; i < posts.length; i++) {
        const post = posts[i];

        let add = true;
        if (carMake && add) {
            add = post.carMake.toLowerCase().indexOf(carMake.toLowerCase()) !== -1;
        }

        if (carModel && add) {
            add = post.carModel.toLowerCase().indexOf(carModel.toLowerCase()) !== -1;
        }

        if (carYear && add) {
            add = post.carYear.toLowerCase().indexOf(carYear.toLowerCase()) !== -1;
        }

        if (category && add) {
            add = post.category.toLowerCase().indexOf(category.toLowerCase()) !== -1;
        }

        if (add) {
            filtered.push(post);
        }
    }

    return filtered;
}
使用

For loop是因为使用此方法,您仅迭代posts一次。如果您不打算更改服务方法,请至少在promise中已解决的componentDidMount内添加此帖子过滤,但是我强烈建议不要在render方法中进行此类操作。

答案 3 :(得分:0)

尝试以下操作(代码注释中的说明):

// 1. don't default category to a placeholder.
// If the value is empty it will default to your empty option,
// which shows the placeholder text in the dropdown.
this.state = {
  posts: [],
  carMake: '',
  carModel: '',
  carYear: '',
  category: ''
}

// 2. write a method to filter your posts and do the filtering in a single pass.
getFilteredPosts = () => {
  const { posts, ...filters } = this.state
  // get a set of filters that actually have values
  const activeFilters = Object.entries(filters).filter(([key, value]) => !!value)
  // return all posts if no filters
  if (!activeFilters.length) return posts

  return posts.filter(post => {
    // check all the active filters
    // we're using a traditional for loop so we can exit as soon as the first check fails
    for (let i; i > activeFilters.length; i++) {
      const [key, value] = activeFilters[i]
      // bail on the first failure
      if (post[key].toLowerCase().indexOf(value.toLowerCase()) < 0) {
        return false
      }
    }
    // all filters passed
    return true
  })
}

// 3. Simplify render
render() {
  // destructure filters so you can just spread them into SideBar
  const { posts, ...filters } = this.state
  const filteredPosts = this.getFilteredPosts()

  return (
    <div className='container-fluid'>
      <div className='row'>
        <div className='col-xl-2 col-lg-3 col-md-4 col-sm-12'>
          <Sidebar
            {...filters}
            handleInputChange={this.handleInputChange}
            handleFormSubmit={this.handleFormSubmit}
          />
        </div>
        <div className='col-xl-8 col-lg-7 col-md-6 col-sm-12 offset-md-1'>
          {filteredPosts.map(post => <PostContainer post={post} key={post.id} />)}
        </div>
      </div>
    </div>
  )
}

要考虑的另一件事是,正在向PostContainer传递作为对象的单个道具post。如果传播该post对象,使其成为props,则可能会大大简化该组件中的prop访问:

{filteredPosts.map(post => <PostContainer key={post.id} {...post} />)}

然后PostContainer中的props.post.id将成为props.id。 props api变得更简单,并且组件也更容易优化(如果有必要的话)。

答案 4 :(得分:0)

我认为您可以使用Lodash _.filter收集方法来帮助您:

Lodash文档:https://lodash.com/docs/4.17.15#filter

多个输入搜索

/*
 * `searchOption` is something like: { carMake: 'Yes', carYear: 2009 }
 */
function filterData(data = [], searchOption = {}) {
  let filteredData = Array.from(data); // clone data
  // Loop through every search key-value and filter them
  Object.entries(searchOption).forEach(([key, value]) => {
    // Ignore `undefined` value
    if (value) {
      filteredData = _.filter(filteredData, [key, value]);
    }
  });
  // Return filtered data
  return filteredData;
}
  

渲染方法

    return (
      <div className='container-fluid'>
        <div className='row'>
          <div className='col-xl-2 col-lg-3 col-md-4 col-sm-12'>
            <Sidebar
              carMake={carMake}
              carModel={carModel}
              carYear={carYear}
              category={category}
              handleInputChange={this.handleInputChange}
              handleFormSubmit={event => {
                event.preventDefault();
                this.handleFormSubmit(event);
              }}
            />
          </div>
          <div className='col-xl-8 col-lg-7 col-md-6 col-sm-12 offset-md-1'>
            {
              filterData(post, { carMake, carModel, carYear, category }).map(post => (
                <PostContainer post={post} key={post.id} />
              ))
             }
          </div>
        </div>
      </div>
    );
  }
}

单输入搜索

或者您可以只有一个搜索输入字段,该字段将过滤整个数据

function filterData(data = [], searchString = '') {
  return _.filter(data, obj => {
    // Go through each set and see if any of the value contains the search string
    return Object.values(obj).some(value => {
      // Stringify the value (so that we can search numbers, boolean, etc.)
      return `${value}`.toLowerCase().includes(searchString.toLowerCase()));
    });
  });
}
  

渲染方法

    return (
      <div className='container-fluid'>
        <div className='row'>
          <div className='col-xl-2 col-lg-3 col-md-4 col-sm-12'>
            <input
              onChange={this.handleInputChange}
              value={this.state.searchString}
            />
          </div>
          <div className='col-xl-8 col-lg-7 col-md-6 col-sm-12 offset-md-1'>
            {filterData(posts, searchString).map(post => <PostContainer post={post} key={post.id} />)}
          </div>
        </div>
      </div>
    );
  }
}

答案 5 :(得分:0)

这可以通过一个过滤器功能来实现。

render() {
  const { carMake, carModel, carYear, category, posts } = this.state;

  const filteredPost = posts.filter(post =>
    post.category.toLowerCase().includes(category.toLowerCase()) && 
    post.carYear === carYear &&
    post.carModel.toLowerCase().includes(carModel.toLowerCase()) && 
    post.carMake.toLowerCase().includes(carMake.toLowerCase())
  );

  return
    ...
    filteredPost.map(post => <PostContainer post={post} key={post.id} />);
}

列表中只有一个循环。不用担心很多ifs和else或三元运算符。只是根据需要进行有序过滤。