Home > Software design >  Creating a filter in React JS
Creating a filter in React JS

Time:07-21

I'm trying to build a game deals watcher and I been working on my browse page. I want to add a filter based on price and title in one fetch request. However, I'm not sure how to accomplish that in my case. Here's my Browse.jsx file:

import React, { useState, useEffect } from 'react';

function Browse({currentUser}) {
    const [gameDealsList, setGameDealsList] = useState([]);
    const [gameTitle, setTitle] = useState('')
    // const [minPrice, setMinPrice] = useState('')
    // const [maxPrice, setMaxPrice] = useState('')

    const defaultURL = `https://www.cheapshark.com/api/1.0/deals?`

    useEffect(()=>{
        fetch(defaultURL)
        .then((r)=>r.json())
        .then((gameList)=> setGameDealsList(gameList))
    },[])

    console.log(gameDealsList)

    function handleRedirect(e, dealID){
        e.preventDefault();
        window.open(`https://www.cheapshark.com/redirect?pageSize=10&dealID=${dealID}`, '_blank');
        return null;
    }

    return(
        <div className="container-fluid">
            <h1>Browse</h1>
            <h4>Filter:</h4>
            <input placeholder='Title' value={gameTitle} onChange={(e)=>setTitle(e.target.value)}></input>
            <span>Price Range $:</span>
            <input
                type="range"
                className="price-filter"
                min="0"
                value="50"
                max="100"
            />
            <br/><br/>

            {gameDealsList.map((game) => 
            <div className="container" key={game.dealID}>
                <div className="row">
                    <div className="col">
                        <img src={game.thumb} className="img-thumbnail" alt='thumbnail'/>
                    </div>
                    <div className="col">
                        <strong><p>{game.title}</p></strong>
                    </div>
                    <div className="col">
                        <span><s>${game.normalPrice}</s></span><br/>
                        <span>${game.salePrice}</span><br/>
                        <span>{Math.round(game.savings)}% Off</span>
                    </div>
                    <div className="col">
                        <button onClick={(e)=>handleRedirect(e, game.dealID)}>Visit Store</button>
                    </div>
                    <div className="col">
                        {currentUser ? <button>Add to wishlist</button> : null}                   
                    </div>
                </div><br/>
            </div>
            )}
            
        </div>
    )
}
export default Browse;

Right now, I'm only fetching deals without any filters. However, the API allows me to set filters. For instance, if I want to search deals based on video game title I can just add &title={random title}. Also, I can type in &upperPrice={maximum price} to set up max price of deals. So, I would like to figure out ways to implement these filters in my fetch request without writing multiple fetch requests.

CodePudding user response:

You can try this approach. Only 1 comment in the code to be worried about. Another thing to worry is to add a Debounce to a fetch function, because without that requests will be sent every time variables in depsArray changed, so if i try to type Nights - 6 requests will be sent while im still typing.

In order to have everything working well:

  1. Create some utils.js file in order to keep some shared helper functions. For debounce in our case.

utils.js

export function debounce(func, wait) {
  let timeout;
  return function (...args) {
    const context = this;
    if (timeout) clearTimeout(timeout);
    timeout = setTimeout(() => {
      timeout = null;
      func.apply(context, args);
    }, wait);
  };
}

  1. Import and wrap the function we want to debounce with useCallback and actual debounce:
import { debounce } from "./utils";

/* ... */

  const fetchDeals = useCallback((queryObject) => {
    const url = new URL(`https://www.cheapshark.com/api/1.0/deals`);

    for (const [key, value] of Object.entries(queryObject)) {
      if (value) url.searchParams.append(key, value);
    }
    console.log(url);
    return fetch(url)
      .then((r) => r.json())
      .then((gameList) => setGameDealsList(gameList));
  }, []);

  const fetchDealsDebounced = useMemo(() => {
    // So API call will not be triggered until 400ms passed since last
    // action that may trigger api call
    return debounce(fetchDeals, 400);
  }, [fetchDeals]);

  // Initial call will still be executed but only once, even if we have
  // 3 items in depsArray (due to debounce)
  useEffect(() => {
    // Name object keys according to what API expects
    fetchDealsDebounced({ title: gameTitle, upperPrice: maxPrice });
  }, [fetchDealsDebounced, gameTitle, maxPrice]);

Edit charming-christian-u94dg2

CodePudding user response:

You should be able to append the query parameters directed by the API to your default query string

fetch(defaultURL   new URLSearchParams({ 
  lowerPrice: minPrice, 
  upperPrice: maxPrice,
  title: gameTitle,
}).then()...

As far as how to control this with only one request you could refactor useEffect like this.

useEffect(() => {
  const queryParams = { 
    lowerPrice: minPrice, 
    upperPrice: maxPrice,
    title: gameTitle,
  };
  if (minPrice === '') delete queryParams.lowerPrice;
  if (maxPrice === '') delete queryParams.upperPrice;
  if (gameTitle === '') delete queryParams.title;
  fetch(defaultURL   new URLSearchParams(queryParams).then()...
}, [maxPrice, minPrice, gameTitle]);
  • Related