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 component (Where the data is actually filtered):
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);
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.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
}]
As can be seen above, I had attempted to just comment out the dropdown's "Disabled" attribute, but that causes it to stop working as a filter completely, 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?
Even 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>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With