0

I have built this custom react hook :

import { useEffect, useContext, useState } from 'react';
import { ProductContext } from '../contexts';
import axios from 'axios';

export default function useProducts(searchQuery) {
  const [products, setProducts] = useContext(ProductContext);
  const [isLoading, setIsloading] = useState(true);

  useEffect(() => {
    axios
      .get(`/shoes?q=${searchQuery ? searchQuery : ''}`)
      .then((res) => {
        setProducts(res.data);
        setIsloading(false);
      })
      .catch((err) => {
        console.error(err);
      });
  }, [searchQuery, setProducts]);

  return { products, isLoading };
}

It basically fetches some data based on a query string that i pass in. The query string comes from an input field :

import React, { useState } from 'react';
import { FiSearch } from 'react-icons/fi';
import { useProducts } from '../../hooks';

export default function SearchBar() {
  const [query, setQuery] = useState('');

  const handleChange = (e) => {
    e.preventDefault();
    setQuery(e.target.value);
  };

  useProducts(query);

  return (
    <div className="search-form">
      <FiSearch className="search-form__icon" />
      <input
        type="text"
        className="search-form__input"
        placeholder="Search for brands or shoes..."
        onChange={handleChange}
      />
    </div>
  );
}

The problem is it will fetch while the user is typing. I want it to fetch after the user didnt type for 500 miliseconds.

What I tried is :

  setTimeout(() => {
    useProducts(query);
  }, 500);

But this will return an error saying :

src\components\header\SearchBar.js Line 14:5: React Hook "useProducts" cannot be called inside a callback. React Hooks must be called in a React function component or a custom React Hook function react-hooks/rules-of-hooks

Search for the keywords to learn more about each error.

Kevin.a
  • 4,094
  • 8
  • 46
  • 82
  • 1
    You are looking for `debounce`. I believe there is already an answer here: https://stackoverflow.com/questions/54666401/how-to-use-throttle-or-debounce-with-react-hook – Dejan Janjušević Dec 28 '20 at 18:39
  • 1
    I would convert custom hook into a custom async function and detect type interval onChange with useRef . Exm. > `time = useRef(Date.now())` and than inside onChange `if (Date.now() > time + 500) { asyn fetch(); time.curent = Date.now() }` – x-magix Dec 28 '20 at 18:58

2 Answers2

1

You can debounce your value with an additional piece of state. Once query is changed, we set off a 500 ms timer that will set the value of debounced. However, if the effect re-runs, we clear that timer and set a new timer.

import React, { useState, useEffect } from 'react';
import { FiSearch } from 'react-icons/fi';
import { useProducts } from '../../hooks';

export default function SearchBar() {
  const [query, setQuery] = useState('');
  const [debounced, setDebounced] = useState('');

  useEffect(() => {
    const timeout = setTimeout(() => {
      setDebounced(query);
    }, 500);
    return () => { clearTimeout(timeout) }
  }, [query])

  const handleChange = (e) => {
    e.preventDefault();
    setQuery(e.target.value);
  };

  useProducts(debounced);

  return (
    <div className="search-form">
      <FiSearch className="search-form__icon" />
      <input
        type="text"
        className="search-form__input"
        placeholder="Search for brands or shoes..."
        onChange={handleChange}
      />
    </div>
  );
}
Nick
  • 16,066
  • 3
  • 16
  • 32
1

I'd change the useProducts hook to accept a debounce time as a parameter, and have it make the axios call only once the debounce time is up:

useProducts(query, 500);
export default function useProducts(searchQuery, debounceTime = 0) {
    const [products, setProducts] = useContext(ProductContext);
    const [isLoading, setIsloading] = useState(true);
    const [timeoutId, setTimeoutId] = useState();
    useEffect(() => {
        clearTimeout(timeoutId);
        setTimeoutId(setTimeout(() => {
            axios
                .get(`/shoes?q=${searchQuery ? searchQuery : ''}`)
                .then((res) => {
                    setProducts(res.data);
                    setIsloading(false);
                })
                .catch((err) => {
                    console.error(err);
                });
        }, debounceTime));
    }, [searchQuery, setProducts]);
    return { products, isLoading };
}
CertainPerformance
  • 356,069
  • 52
  • 309
  • 320