admin管理员组文章数量:1391798
What I currently have "works", however each parameter depends on the last. My goal was to allow the user to use any amount of the search fields to filter through posts, but can't seem to be able to wrap my head around how to actually execute it.
Code for the search fields:
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;
Here is the code for the parent ponent (Where the data is actually filtered):
import React, { Component } from 'react';
import Sidebar from '../../ponents/Sidebar';
import API from '../../utils/API';
import PostContainer from '../../ponents/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();
}
ponentDidMount() {
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);
}
});
}
ponentWillUnmount() {
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);
The data returned from the API is in the form of an array of objects as follows:
[{
"id":4,
"title":"1995 Toyota Supra",
"desc":"asdf",
"itemImg":".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":".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
}]
As can be seen above, I had attempted to just ment out the dropdown's "Disabled" attribute, but that causes it to stop working as a filter pletely, and returns all results no matter the selection. This is caused by my mess of ternary operators checking for each filter. Is there a better way I could be doing this?
What I currently have "works", however each parameter depends on the last. My goal was to allow the user to use any amount of the search fields to filter through posts, but can't seem to be able to wrap my head around how to actually execute it.
Code for the search fields:
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;
Here is the code for the parent ponent (Where the data is actually filtered):
import React, { Component } from 'react';
import Sidebar from '../../ponents/Sidebar';
import API from '../../utils/API';
import PostContainer from '../../ponents/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();
}
ponentDidMount() {
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);
}
});
}
ponentWillUnmount() {
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);
The data returned from the API is in the form of an array of objects as follows:
[{
"id":4,
"title":"1995 Toyota Supra",
"desc":"asdf",
"itemImg":"https://i.imgur./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./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
}]
As can be seen above, I had attempted to just ment out the dropdown's "Disabled" attribute, but that causes it to stop working as a filter pletely, and returns all results no matter the selection. This is caused by my mess of ternary operators checking for each filter. Is there a better way I could be doing this?
Share Improve this question asked Jul 9, 2019 at 0:12 RevircsRevircs 1,3523 gold badges14 silver badges25 bronze badges 1- Can you post a codesandbox/stackblitz link reproducing the issue? – Munim Munna Commented Jul 22, 2019 at 15:11
6 Answers
Reset to default 6 +25Even though the answer from @Nina Lisitsinskaya is correct, I would not have a huge list of if
s and have all just done with a filter concatenation.
That way adding another way to filter is easier and quite readable. The solution though is similar.
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)
...
}
Of course then later you want to use filtered
similarly to this in the JSX expression, otherwise there is nothing to show.
...
<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>
There is no need to use terrible huge ternary operators in the JSX at all. First you can filter the collection sequentially with each filter:
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);
}
...
Then you can just use filtered
in the JSX expression:
...
<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>
You should never do such calculations in your render
method - it should work with clean calculated state/props
. Basically filtering should happen on your backend
, but if you want to filter on frontend
you should move filtering logic to service method, something like this:
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
is used because with this approach you iterate posts
only once. If you are not going to change your service method, at least add this post filtering inside of your resolved promise
in ponentDidMount
, but I stronlgy advise not to do such things in render
method.
Try the following (instructions in code ments):
// 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>
)
}
Another thing to consider is that PostContainer
is being passed a single prop post
that is an object. You could probably simplify prop access quite a bit in that ponent if you spread that post object so it became the props:
{filteredPosts.map(post => <PostContainer key={post.id} {...post} />)}
Then in PostContainer
, props.post.id
would bee props.id
. The props api bees simpler and the ponent will be easier to optimize (if that bees necessary).
I think you can use Lodash _.filter
collection method to help you:
Lodash documentation: https://lodash./docs/4.17.15#filter
Multiple inputs search
/*
* `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;
}
render method
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>
);
}
}
Single input search
Or you can have one single search input field, and that will filter the whole data
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()));
});
});
}
render method
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>
);
}
}
This can be achieved with one single filter function.
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} />);
}
Just one single loop through the list. No hassle of lots of ifs and else, or ternary operators. Just ordered way of filtering according to the need.
本文标签:
版权声明:本文标题:reactjs - How can I create a search with multiple, optional, parameters using JavaScript? - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1740831496a2293014.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论