How to implement rtk's createApi query for debouncing
Asked Answered
P

3

8

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

Thanks in advance.

Penitent answered 10/7, 2021 at 17:26 Comment(3)
This needs a lot of clarification on what exactly you want to debounce in which scenario.Legume
@phry, i want to debounce the keypress(keyboard) event for limiting the search api call, and the component i am using is functional component, search should with minimum 2 characters.Penitent
then use a second local state for "syncing" that up. first local state follows your input, second local state "lags behind", debounced. value from second locatl state is used as input to useQueryLegume
J
17

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.

Jennie answered 27/7, 2021 at 18:13 Comment(0)
T
1

after the first render our request hook will try to send the request we can bypass this with the skipToken the request will not be sent until searchTerm returns some value

import { useDebounce } from 'use-debounce'
import { skipToken } from '@reduxjs/toolkit/query'

const [storeName, setStoreName] = useState('')
const [searchTerm] = useDebounce(storeName, 1500)


const { data } = useSearchStoresRequestQuery(searchTerm || skipToken)

more about skipToken: https://redux-toolkit.js.org/rtk-query/usage/conditional-fetching

and also inside my useSearchStoresRequestQuery

 endpoints: (builder) => ({
      getStoresWithSearchRequest: builder.query({
         query: ({searchTerm}) => {
            return {
               url: `admin/v1/stores?searchTerm?${searchTerm}`,
               method: 'GET',
       
            }
         },
Testate answered 15/12, 2022 at 7:26 Comment(0)
I
0

In my case the following solution worked well.

I used component way of debouncing. Use any debounce fn, in my way:

npm i debounce

And then in component

import debounce from 'debounce';

const Component = () => {
  const { data, refetch } = useYourQuery();
  const [mutate] = useYourMutation();

  const handleDebouncedRequest = debouce(async () => {
        try {
            // you can use refetch or laze query way
            await refetch();
            await mutate();
        } catch {}
    }, 2000);

   // ...other component logic
}
Infecund answered 3/6, 2022 at 11:0 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.