0

I'm building an online store in React and decided to use React Query for the project. The task is to display and store products in the cache. From these products, I need to fetch the required one on the product page and render it. There are two scenarios:

  1. The product is in the cache - I retrieve it from the cache and return it from the function.
  2. It's not in the cache - I fetch it from the server, load it into the cache, and return it from the function. The problem is that I don't understand how to update the data in the cache.

Here's my custom hook:

// useProducts.ts

import { IProduct } from '@/interfaces/product.interface';
import ProductService from '@/services/product/product.service';
import { useQuery, useQueryClient } from '@tanstack/react-query';

export const useProducts = () => {
  const queryClient = useQueryClient();

  const { data, isSuccess } = useQuery({
    queryKey: ['products'],
    queryFn: () => ProductService.getAll({ page: 1, perPage: 9 }),
    select: ({ data }) => data,
  });

  const getProductById = async (productId: number) => {
    console.log("cached products: ", queryClient.getQueryCache().find({ queryKey: ['products'] })?.state.data?.data);
    const product = data?.products.find((p) => p.id === productId);
    if (!!product) return product;

    try {
      const response = await ProductService.getById(productId);
      
      queryClient.setQueryData<IProduct[]>(['products'], (state) => {
        console.log("state:", state);
        return [...(state || []), response];
      });

      return response;
    } catch (error) {
      console.error('Error fetching product by ID:', error);
      return null;
    }
  };

  return { data, isSuccess, getProductById };
};

// ProductService.ts

import { $axios } from '@/api/api.interceptor';
import { IProduct } from '@/interfaces/product.interface';
import {
  IProductResponse,
  ProductDataFiltersType,
  ProductDataType,
} from './product.types';

class ProductService {
  private url = '/product';

  getAll = async (queryData = {} as ProductDataFiltersType) => {
    return $axios.get<IProductResponse>(this.url, { params: queryData });
  };

  getById = async (id: string | number) => {
    // return $axios.get<IProduct>(`${this.url}/${id}`);
    const response = await $axios.get<IProduct>(`${this.url}/${id}`);
    return response.data;
  };

  ...
}

export default new ProductService();

export type IProductResponse = {
  products: IProduct[]
  currentPage: number
  totalPages: number
}

For some reason, when I call setQueryData, the axios response is in the state instead of the array of products (as I think it should work):

cached products: {products: Array(9), totalPages: 2, currentPage: 1}
state: {data: {products, currentPage, totalPages}, status: 200, statusText: 'OK', headers: AxiosHeaders, config: {…}, …}

Consequently, my error is -

useProducts.ts:42 Error fetching product by ID: TypeError: (state || []) is not iterable
    at useProducts.ts:25:21
    at functionalUpdate (@tanstack_react-query.js?v=6de31b48:42:42)
    at _a8.setQueryData (@tanstack_react-query.js?v=6de31b48:1538:18)
    at getProductById (useProducts.ts:24:19)

Btw, products in my Home.tsx works perfectly.

import CardsContainer from '@/components/shared/CardsContainer/CardsContainer';
import { useProducts } from '@/hooks/features/products/useProducts';
import { useActions } from '@/hooks/general/useActions';
import { useEffect } from 'react';
import Banner from './components/Banner/Banner';

function Home() {
  const { data, isSuccess } = useProducts();
  const { checkAuth } = useActions();

  useEffect(() => {
    checkAuth();
  }, []);

  return (
    <section className="rows-container">
      <Banner />
      {isSuccess && data?.products && (
        <CardsContainer products={data.products} />
      )}
    </section>
  );
}

export default Home;

P.S.

  1. Can you explain to a newbie the difference between setQueryData and setQueriesData?
  2. Is there a way for me to see the data that React Query stores in the cache (Maybe there are some extensions)?
14
  • Your life would be made immensely simpler if you just had a useProductById hook that just uses a useQuery function directly and ignore the possibility that the product is in the products cache entirely. Stay away, as much as possible, from manually checking/manipulating the cache, pretend it doesn’t exist. Commented Jan 1, 2024 at 20:35
  • 1
    setQueriesData is used to update multiple queries while setQueryData is for a single query. setQueriesData internally calls setQueryData. As for your second question, you can use Tanstack query devtools tanstack.com/query/latest/docs/react/devtools Commented Jan 1, 2024 at 20:35
  • What you are doing is also a common pattern to use two separate useQuery’s for. It’s not a special use case, it’s a well known one to have a list and detail view. This may help tkdodo.eu/blog/… Commented Jan 1, 2024 at 20:38
  • How correct is it to use two useQuery (for all products and for each individual one)? It sounds kind of weird to me. It's like we're storing all the data in two copies. Commented Jan 1, 2024 at 20:42
  • If I store two useQuery, it may turn out like this: we went to the product page - it loaded into the useQuery for single products. Then we went to the main page and uploaded the goods to another useQuery for all products. And then, when we need to get something specific, there will be big problems. Commented Jan 1, 2024 at 20:47

1 Answer 1

0
  1. You don't need to set cache, all cache is settled by react-query automatically if you provide 'queryKey' when fetching the data.

  2. To access the cache data you can use const queryClient = useQueryClient() const data = queryClient.getQueryData(queryKey)

  3. The package '@tanstack/react-query' includes an asynchronous function that can be used to get an existing query's cached data. If the query does not exist fetchQuery will be called and its results returned. const data = await queryClient.ensureQueryData({ queryKey, queryFn }) You can check the docs here - https://tanstack.com/query/v4/docs/react/reference/QueryClient#queryclientensurequerydata

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

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.