我目前有“作品”,但是每个参数都取决于最后一个。我的目标是允许用户使用任何数量的搜索字段来过滤帖子,但似乎无法将我的头全围绕如何实际执行。
搜索字段的代码:
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”属性,但是这导致它完全停止充当过滤器,并且无论选择如何都返回所有结果。这是由于我的三元运算符检查每个过滤器而造成的。有没有更好的方法可以做到这一点?
答案 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或三元运算符。只是根据需要进行有序过滤。