Search code examples
reactjsreduxredux-toolkitrtk-query

How to implement rtk's createApi query for debouncing


Can someone help me in implementing the debounce functionality using creatApi with query implementation from redux toolkit.

Thanks in advance.


Solution

  • I personally didn't find any debounce implementation in RTK Query out-of-the-box. But you can implement it yourself.

    Define an api. I'm using an openlibrary's one:

    import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
    
    type BooksSearchResult = {
      docs: Book[];
    };
    
    type Book = {
      key: string;
      title: string;
      author_name: string;
      first_publish_year: number;
    };
    
    export const booksApi = createApi({
      reducerPath: 'booksApi',
      baseQuery: fetchBaseQuery({ baseUrl: 'http://openlibrary.org/' }),
      endpoints: builder => ({
        searchBooks: builder.query<BooksSearchResult, string>({
          query: term => `search.json?q=${encodeURIComponent(term)}`,
        }),
      }),
    });
    
    export const { useSearchBooksQuery } = booksApi;
    

    Next thing you need is debounce hook, which guarantees that some value changes only after specified delay:

    function useDebounce(value: string, delay: number): string {
      const [debouncedValue, setDebouncedValue] = useState(value);
    
      useEffect(() => {
        const handler = setTimeout(() => {
          setDebouncedValue(value);
        }, delay);
    
        return () => {
          clearTimeout(handler);
        };
      }, [value, delay]);
    
      return debouncedValue;
    }
    

    Use debounce hook on your search form:

    import React, { useEffect, useState } from "react";
    import BookSearchResults from "./BookSearchResults";
    
    function useDebounce(value: string, delay: number): string {
      const [debouncedValue, setDebouncedValue] = useState(value);
    
      useEffect(() => {
        const handler = setTimeout(() => {
          setDebouncedValue(value);
        }, delay);
    
        return () => {
          clearTimeout(handler);
        };
      }, [value, delay]);
    
      return debouncedValue;
    }
    
    const DebounceExample: React.FC = () => {
      const [searchTerm, setSearchTerm] = useState("");
      const debouncedSearchTerm = useDebounce(searchTerm, 500);
    
      return (
        <React.Fragment>
          <h1>Debounce example</h1>
          <p>Start typing some book name. Search starts at length 5</p>
          <input
            className="search-input"
            type="text"
            placeholder="Search books"
            value={searchTerm}
            onChange={(e) => setSearchTerm(e.target.value)}
          />
          <BookSearchResults searchTerm={debouncedSearchTerm}></BookSearchResults>
        </React.Fragment>
      );
    };
    
    export default DebounceExample;
    
    

    Use the search query hook in search results component. It uses its own state for search term value, which is very convenient if you want to add extra "filters" for debounced value (for example, start query only when search term's length is greater than some value).

    import React, { useState, useEffect } from "react";
    import { useSearchBooksQuery } from "./booksApi";
    
    type BookSearchResultsProps = {
      searchTerm: string;
    };
    
    const BookSearchResults: React.FC<BookSearchResultsProps> = ({
      searchTerm
    }: BookSearchResultsProps) => {
      const [filteredSearchTerm, setFilteredSearchTerm] = useState(searchTerm);
      const { data, error, isLoading, isFetching } = useSearchBooksQuery(
        filteredSearchTerm
      );
      const books = data?.docs ?? [];
    
      useEffect(() => {
        if (searchTerm.length === 0 || searchTerm.length > 4) {
          setFilteredSearchTerm(searchTerm);
        }
      }, [searchTerm]);
    
      if (error) {
        return <div className="text-hint">Error while fetching books</div>;
      }
    
      if (isLoading) {
        return <div className="text-hint">Loading books...</div>;
      }
    
      if (isFetching) {
        return <div className="text-hint">Fetching books...</div>;
      }
    
      if (books.length === 0) {
        return <div className="text-hint">No books found</div>;
      }
    
      return (
        <ul>
          {books.map(({ key, title, author_name, first_publish_year }) => (
            <li key={key}>
              {author_name}: {title}, {first_publish_year}
            </li>
          ))}
        </ul>
      );
    };
    
    export default BookSearchResults;
    

    Full example is available here.