reactjstypescriptreduxrtk-query

RTK Query caching between endpoints


I'm having trouble understanding the caching behavior with RTK query.


import React, { useState } from 'react';
import { createRoot } from 'react-dom/client';
import { Provider } from 'react-redux';
import { createApi } from '@reduxjs/toolkit/query/react';
import {combineReducers, configureStore } from '@reduxjs/toolkit';

const baseQuery = async (arg: string) => {
  await new Promise(resolve => setTimeout(resolve, 5000)); 
  return { data: arg == 'odd' ? [1, 2, 3] : [2, 4, 6]}; 
}; 

const api = createApi({
  baseQuery,
  endpoints: (build) => ({
    fetchOdd: build.query<number[], number>({query: () => 'odd', providesTags: ['odd']}),
    fetchEven: build.query<number[], number>({query: () => 'even', providesTags: ['even']})
  })
});

const store = configureStore({
  reducer: combineReducers({ [api.reducerPath]: api.reducer }),
  middleware: (getDefault) => getDefault().concat(api.middleware)
});


const App = () => {

  const [dataset, setDataset] = useState('odd'); 
  const result = dataset == 'odd' ? api.endpoints.fetchOdd.useQuery(1) : api.endpoints.fetchEven.useQuery(2)
  return (
    <div>
      <button onClick={() => setDataset(dataset == 'odd' ? 'even' : 'odd')}>{dataset}</button>
      { result.data?.map(d => <div key={d}>{d}</div>) }
    </div>
    );
}

const container = document.getElementById('app-root')!
const root = createRoot(container);
root.render(<Provider store={store}><App/></Provider>);  

This mostly works as expected - however when switching between datasets the previous dataset will still be displayed while waiting for the next fetch to complete. I would have expected this to be empty since it is for a different endpoint that hasn't been fetched yet?

Also, the arguments to useQuery are not needed for any logic, however if I do not have different arguments for each endpoint then the second fetch will not run at all and just sits in pending.

Any help understanding this behavior or achieving the desired results (result.data empty when switched to the next endpoint, and ideally getting rid of the unneeded argument) would be appreciated. cheers.


Solution

  • This mostly works as expected - however when switching between datasets the previous dataset will still be displayed while waiting for the next fetch to complete. I would have expected this to be empty since it is for a different endpoint that hasn't been fetched yet?

    You are basically breaking React's Rules of Hooks by conditionally calling the useQuery hooks, but I suspect that each instance is similar enough that React isn't aware that a technically different hook was called on a subsequent render cycle. Without doing a complete deep dive into the code execution I believe this is roughly what is occurring:

    Also, the arguments to useQuery are not needed for any logic, however if I do not have different arguments for each endpoint then the second fetch will not run at all and just sits in pending.

    I think this is related to the above where because the query argument didn't change that it doesn't re-run. Toggling between arguments 1 and 2 gets past this and triggers the second query to run.

    Any help understanding this behavior or achieving the desired results (result.data empty when switched to the next endpoint, and ideally getting rid of the unneeded argument) would be appreciated.

    I suggest unconditionally calling both hooks (it's the rule anyway). Use the skip option to conditionally run the query if you must. See Conditional Fetching.

    Example:

    const [dataset, setDataset] = useState("odd");
    
    const evenResult = api.endpoints.fetchEven.useQuery(undefined, {
      skip: dataset !== "even",
    });
    const oddResult = api.endpoints.fetchOdd.useQuery(undefined, {
      skip: dataset !== "odd",
    });
    
    const result = dataset == "odd" ? oddResult : evenResult;
    

    or using the generated query hooks:

    const [dataset, setDataset] = useState("odd");
    
    const evenResult = api.useFetchEvenQuery(undefined, {
      skip: dataset !== "even",
    });
    const oddResult = api.useFetchOddQuery(undefined, {
      skip: dataset !== "odd",
    });
    
    const result = dataset == "odd" ? oddResult : evenResult;