Queries with RTK Query

RTK Query is a caching data-fetching layer built into Redux Toolkit 3,824 , imported from @reduxjs/toolkit/query/react. You describe endpoints once; it generates a slice, a middleware and a Hook per endpoint, and handles deduplication, caching, loading flags, refetching and invalidation — replacing everything you would otherwise hand-write for server data.

An API slice and the Hook it generatesJavaScript
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
export const catalogApi = createApi({
  reducerPath: 'catalogApi',                                  // where the cache lives
  baseQuery: fetchBaseQuery({ baseUrl: 'https://api.example.com/' }),
  tagTypes: ['Product'],
  endpoints: (build) => ({
    getProducts: build.query({
      query: (page = 1) => `products?page=${page}`,           // the argument is the key
      providesTags: (result = []) => result.map(p => ({ type: 'Product', id: p.id })),
    }),
  }),
});
export const { useGetProductsQuery } = catalogApi;            // generated, one per endpoint
export const store = configureStore({
  reducer: { [catalogApi.reducerPath]: catalogApi.reducer },
  middleware: (g) => g().concat(catalogApi.middleware),       // required: drives the cache
});
function Products({ page }) {
  const { data, error, isLoading, isFetching } = useGetProductsQuery(page);
  if (isLoading) return <p>Loading...</p>;                    // first load for this hook
  if (error) return <p role="alert">Request failed</p>;
  return <ul style={{ opacity: isFetching ? 0.5 : 1 }}>       {/* background refetch */}
    {data.map(p => <li key={p.id}>{p.name}</li>)}</ul>;
}

Two flags, two meanings. isLoading is true only while there is no data at all for this Hook; isFetching is true whenever a request is in flight, including the refetch after a page change. Dimming the old page instead of replacing it with a spinner is what makes pagination feel instant.

The cache is keyed by endpoint plus serialized argument, so two components calling useGetProductsQuery(1) share one request and one entry, while 2 starts a second entry. A query can be held back with { skip: !id }, deferred with useLazyGetProductsQuery(), or narrowed with selectFromResult so the component re-renders only when the part it displays changes.