goodpixels
goodpixels

Reputation: 523

What is the best way to filter data in React?

I'm building a catalogue of restaurants in the country. The API returns an array of objects, each one consisting of fields such as restaurantName, restaurantLocation, restaurantPriceRange.

I want to create a filtering component, which will reduce the array of restaurants to a subset containing only those that match the search criteria. I also need to pass the filter values via the URL, for which I believe I should use a router.

I was thinking about storing the initial data set with useState hook and then using useReducer to store active filter selection and a subset of the filtered restaurant collection with only those matching the search criteria, but I feel like this is a clumsy solution, as effectively I would be duplicating a subset of the entire restaurant collection.

However, if I do not store the initial data set in a separate object using useState, all non-matches will be gone forever once the filters have been applied.

What would be the best way to achieve this?

DISCLAIMER: I know how to filter arrays in JavaScript and am fully aware of functions such as filter, map, sort, etc. This question is about the React ecosystem and what would be the cleanest, simplest and best way to structure my code in a React application. I know that I will still have to use Array.prototype.filter in my reducer when I write the code for it.

Upvotes: 3

Views: 8345

Answers (3)

Fahimeh Ahmadi
Fahimeh Ahmadi

Reputation: 1029

I used this method

import React, { useEffect, useState } from 'react';
    import { Link, useNavigate } from 'react-router-dom';
    import swal from 'sweetalert';
    import axios from 'axios';


    const Users = () => {
        const navigate = useNavigate();
        const [users, setUsers] = useState([]);
        const [mainUsers, setMainUsers] = useState([]);
        useEffect(() => {
            axios.get('https://jsonplaceholder.typicode.com/users').then(res => {
                setUsers(res.data);
                setMainUsers(res.data);
            }).catch(err => {
                console.log(err);
            })
        }, []);
       
        const handleSearch = (e) => {
            setUsers(mainUsers.filter(u => u.name.toLowerCase()
            .includes(e.target.value.toLowerCase())
            ));
            console.log(e.target.value);
        }
    return (
        <div className={`mt-5 p-4 container-fluid`}>
            <div className='row my-2 mb-4 justify-content-between w-100 mx-0'>
                <div className='form-group col-10 col-md-6 col-lg-4'>
                    <input type="text" className='form-control shadow' 
                placeholder='Search' onChange={handleSearch} />
                </div> 
            </div>

            {users.length ? (
                <table className='table'>
                    <thead>
                        <tr>
                            <th scope="col">#</th>
                            <th scope="col">Name</th>
                            <th scope="col">User Name</th>
                            <th scope="col">Email</th>
                            <th scope="col">Action</th>
                        </tr>
                    </thead>
                    <tbody>
                        {users.map(u =>
                            <tr key={u.id}>
                                <th scope="row">{u.id}</th>
                                <td>{u.name}</td>
                                <td>{u.username}</td>
                                <td>{u.email}</td>
                                <td>
                                   
                                      <i className='fa fa-edit text-warning'></i>
                                    
                                    <i className='fa fa-edit text-warning pointer'></i>
                                </td>
                            </tr>
                        )}
                    </tbody>
                </table>
            ) : (
                <h6 className='text-center text-info'>wating ...</h6>
            )}

        </div>
    )
}
export default Users;

Upvotes: 0

Linda Paiste
Linda Paiste

Reputation: 42188

You are asking for opinions so here is mine. React state (whether it’s useState or useReducer) should only be used for stateful values — the values which change and cause your rendered data to change. The filtered list of restaurants is not a stateful value. The filters are a stateful value, and the filtered list is derived from that state. The filtered list should be a const that you generate on each render. If there are other states or props in your component that change which do not effect the filtered list, you can use the useMemo hook to memoize the filtered list to prevent unnecessary recalculations.

Pseudo-code

import React, {useMemo} from “react”;
import {useLocation} from “react-router-dom”;

export const MyComponent = ({restaurants}) => {

   const location = useLocation();

   const filteredRestaurants = useMemo( () => {
       return restaurants.filter( /* some code */ );
   }, [location, restaurants] ); //dependency on location and restaurants 

   return (
      <Wrapper>
        <EditFiltersComponent />
        <List>
           {filteredRestaurants.map( restaurant => 
              <Restaurant {...restaurant} />
           )}
        </List>
      </Wrapper>
   )
}

Upvotes: 6

Daniil Loban
Daniil Loban

Reputation: 4381

Generally we get data from props

const OurComponent = ({data, filter}) => { . . .

or

const { filter } = useParams(); // via query string

Also you may have filter input fields in this component then store their values ​​in a local state for a rerender.

For a complex filter is good idea to have filter function:

const filterFunc = ({address}) => {
  address.toLowerCase().includes(filter.address.toLowerCase());
}

and render will be like

return (
  <ul>
    { 
      data.filter(filterFunc).map({id, name, address} => (
        <li key={id}>{`${name} - ${address}`}</li> 
      ))
    }
  </ul>
) 

Upvotes: 0

Related Questions