Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I create a search with multiple, optional, parameters using JavaScript?

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?

like image 477
Revircs Avatar asked Jul 09 '19 00:07

Revircs


2 Answers

Even though the answer from @Nina Lisitsinskaya is correct, I would not have a huge list of ifs 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>
like image 161
Alejandro Vales Avatar answered Oct 12 '22 15:10

Alejandro Vales


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>
like image 25
Nina Lisitsinskaya Avatar answered Oct 12 '22 15:10

Nina Lisitsinskaya