1

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.

1
  • You could write a function that handles that. e.g.function fetchDeals(filter) { /...} Commented Jul 20, 2022 at 15:30

2 Answers 2

1

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

Sign up to request clarification or add additional context in comments.

8 Comments

It throws me a failed fetch error...
Check your browser dev tools network tab, see what is the response from the server
I get status 429 ( Too many requests) and Access to fetch at 'cheapshark.com/api/1.0/…' from origin 'localhost:4000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin'
@Jeffrey As per apidocs.cheapshark.com Excessive automated requests to build a cached catalog of data will run into rate limiting issues and should be avoided. Due to react's nature in terms of almost constant rerendering of everything, useEffect might got called too frequently, same could happen due to not implemented debounce functionality, APIs usually block users if they are making too many requests in short period of time. Not sure about how to get unbanned on that api you have. Probably in a hour it will "cool down".
@Jeffrey done, CodeSandbox example is also updated
|
0

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]);

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.