[英]How can I create a search with multiple, optional, parameters using JavaScript?
我目前所擁有的“工作”,但每個參數取決於最后一個。 我的目標是允許用戶使用任意數量的搜索字段來過濾帖子,但似乎無法理解如何實際執行它。
搜索字段的代碼:
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
}]
從上面可以看出,我曾嘗試只注釋下拉列表的“已禁用”屬性,但這會導致它完全停止作為過濾器工作,並返回所有結果,無論選擇如何。 這是由於我的三元運算符混亂檢查每個過濾器造成的。 有沒有更好的方法可以做到這一點?
即使@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>
根本不需要在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>
你永遠不應該在你的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
。 如果您不打算更改服務方法,至少在componentDidMount
中的已解析的promise
中添加此post過濾,但我強烈建議不要在render
方法中執行此類操作。
請嘗試以下(代碼注釋中的說明):
// 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
就是一個對象。 如果您傳播該帖子對象使其成為道具,您可以在該組件中簡化道具訪問:
{filteredPosts.map(post => <PostContainer key={post.id} {...post} />)}
然后在PostContainer
, props.post.id
將成為props.id
。 道具api變得更簡單,組件將更容易優化(如果有必要)。
我想你可以使用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>
);
}
}
這可以通過一個單一的過濾功能來實現。
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和其他三元運算符的麻煩。 只是根據需要訂購過濾方式。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.