Cache or state, try React-query

Sky and sea
Sky and sea

Introduction

A popular library for working with the state of web applications in react-js is redux. However, it has a number of disadvantages such as verbosity (even in conjunction with redux-toolkit), the need to select an additional layer (redux-thunk, redux-saga, redux-observable). There is a feeling that somehow this is all too complicated and for a long time there were hooks and in particular the useContext hook .. So I tried another solution.





Test application 

Β« Β» create react app, typescript, redux-toolkit, redux saga. redux context + react-query. , , , react-query  . .. , .. ,   . .. .





Test application screens

 

react-query , , .. redux 2 . β€” , . β€” , . 





react-context. :





export const CitiesProvider = ({
  children,
}: {
  children: React.ReactNode;
}): JSX.Element => {
  const [citiesState, setCitiesState] = useLocalStorage<CitiesState>(
    'citiesState',
    citiesStateInitValue,
  );

  const addCity = (id: number) => {
    if (citiesState.citiesList.includes(id)) {
      return;
    }
    setCitiesState(
      (state: CitiesState): CitiesState => ({
        ...state,
        citiesList: [...citiesState.citiesList, id],
      }),
    );
  };
 // removeCity..,  setCurrentCity..

  return (
    <itiesContext.Provider
      value={{
        currentCity: citiesState.currentCity,
        cities: citiesState.citiesList,
        addCity,
        removeCity,
        setCurrentCity,
      }}
    >
      {children}
    </itiesContext.Provider>
  );
};

      
      



, setCurrentCity, removeCity



. , localStorage . , , , , .





React-query

, , react-query.   :





import { QueryClient, QueryClientProvider } from 'react-query';
import { ReactQueryDevtools } from 'react-query/devtools';
 
import { CitiesProvider } from './store/cities/cities-provider';
 
const queryClient = new QueryClient();
 
ReactDOM.render(
  <React.StrictMode>
    <QueryClientProvider client={queryClient}>
      <CitiesProvider>
        <App />
      
      



:





const queryCities = useQuery('cities', fetchCitiesFunc);
const cities = queryCities.data || [];
      
      



'cities'



, . - , Promise, . .





useQuery UseQueryResult



, ,





const { isLoading, isIdle, isError, data, error } = useQuery(..
      
      







export function useCurrentWeather(): WeatherCache {
  const { currentCity } = useContext(itiesContext);
 
 //   
  const queryCities = useQuery('cities', fetchCitiesFunc, {
     refetchOnWindowFocus: false,
    staleTime: 1000 * 60 * 1000,
  });
  const citiesRu = queryCities.data || [];
 
//    .. 
 const city = citiesRu.find((city) => {
    if (city === undefined) return false;
    const { id: elId } = city;
    if (currentCity === elId) return true;
    return false;
  });
 
  const { id: weatherId } = city ?? {};
 
 //   
  const queryWeatherCity = useQuery(
    ['weatherCity', weatherId],
    () => fetchWeatherCityApi(weatherId as number),
    {
      enabled: !!weatherId,
      staleTime: 5 * 60 * 1000,
    },
  );
 
  const { coord } = queryWeatherCity.data ?? {};
 
 //      . 
  const queryForecastCity = useQuery(
    ['forecastCity', coord],
    () => fetchForecastCityApi(coord as Coord),
    {
      enabled: !!coord,
      staleTime: 5 * 60 * 1000,
    },
  );
 
  return {
    city,
    queryWeatherCity,
    queryForecastCity,
  };
}
      
      



staleTime



β€” , , . , . , staleTime =0



.





 enabled: !!weatherId



, . useQuery



isIdle



. .





const queryWeatherCity = useQuery(['weatherCity', weatherId],.. 
      
      



, , + .





:





export function Forecast(): React.ReactElement {
  const {
    queryForecastCity: { isFetching, isLoading, isIdle, data: forecast },
  } = useCurrentWeather();
 
  if (isIdle) return <LoadingInfo text="   " />;
  if (isLoading) return <LoadingInfo text="  " />;
 
  const { daily = [], alerts = [], hourly = [] } = forecast ?? {};
  const dailyForecastNext = daily.slice(1) || [];
 
  return (
    <>
      <Alerts alerts={alerts} />
      <HourlyForecast hourlyForecast={hourly} />
      <DailyForecast dailyForecast={dailyForecastNext} />
      {isFetching && <LoadingInfo text="  " />}
    </>
  );
}
      
      



isLoading β€” isFetching - .





React-query . Redux, ( ) 





Developer tools window

, Actions, , , .. , , , . . :





import { ReactQueryDevtools } from 'react-query/devtools';
      
      



, process.env.NODE_ENV === 'production'



,  . Create React App .





react-query , , , .





  • useQueries



    . .. useQuery



    .





const userQueries = useQueries(
  users.map(user => {
    return {
      queryKey: ['user', user.id],
      queryFn: () => fetchUserById(user.id),
    }
  })
      
      



  • , , 3 . retry



    .





  • , , useMutations







const mutation = useMutation(newTodo => axios.post('/todos', newTodo))
      
      



  • , useInfiniteQuery







  • , , , .





After replacing redux-toolkit + redux-saga and context + react-query, the code seemed much easier to me and I got more functionality out of the box for working with requests to the server. However, the react-context part does not have special debugging tools and generally raises concerns, but it turned out to be quite small and react-devtools was enough for me. In general, I am satisfied with the react-query library and, in general, the idea of ​​separating the cache into a separate entity seems interesting to me. But still this is a very small application with several get requests ..   





Links

Layout is correct only for mobile devices





There is a branch with redux





React-query documentation








All Articles