reactjsreduxredux-toolkitrtk-query

Confusion with Additional Properties Stored in Redux State When Using RTK Query


I've been using Redux Toolkit for a while with async thunks making calls using Axios. I find it super convenient that calling the thunk in dispatch directly saves my response in the store, allowing me to use it throughout the app wherever I want. This approach, however, always required manual loading states and error handling.

Recently, I heard good things about RTK Query, especially its provided loading and error states, and the additional benefit of no longer needing Axios, which is one less library in the bundle.

Since I started working more with RTK Query, there have been some aspects that don't make sense to me. For example, similar to asyncThunk, RTK Query saves the returned data to the store, but it also stores a lot of other properties I don't really need.

If I want to access the data I fetched from the API, do I have to get it like this?

const data = useSelector((state: any) => state.todoAPI.queries.getTodos);

This seems like a lot of object drilling just to get the response of an API call. On top of that, I don't need data like status, startedTimestamp, and fulfilledTimestamp stored in the Redux store. Why do I have to carry these in the state?

Is there a way to simplify accessing just the response data and avoid storing unnecessary properties in the state? Any guidance or best practices would be greatly appreciated!


Solution

  • If I want to access the data I fetched from the API, do I have to get it like this?

    const data = useSelector((state: any) => state.todoAPI.queries.getTodos);
    

    This seems like a lot of object drilling just to get the response of an API call. On top of that, I don't need data like status, startedTimestamp, and fulfilledTimestamp stored in the Redux store. Why do I have to carry these in the state?

    Yes, while the API slice is built over top createSlice and createAsyncThunk and all the endpoint data is stored in the store, it is not the intention that you manually select that state. It's what RTKQ uses to manage endpoint subscriptions. Think of most of the API slice state as being just implementation details.

    Is there a way to simplify accessing just the response data and avoid storing unnecessary properties in the state?

    Redux-Toolkit Query exports a React module that includes automatic generation of React hooks for each endpoint definition, see API Slices: React Hooks for details.

    The core RTK Query createApi method is UI-agnostic, in the same way that the Redux core library and Redux Toolkit are UI-agnostic. They are all plain JS logic that can be used anywhere.

    However, RTK Query also provides the ability to auto-generate React hooks for each of your endpoints. Since this specifically depends on React itself, RTK Query provides an alternate entry point that exposes a customized version of createApi that includes that functionality:

    import { createApi } from '@reduxjs/toolkit/query/react'
    

    The basic change is importing createApi from "@reduxjs/toolkit/query/react" instead of the regular "@reduxjs/toolkit/query" export.

    Ensure you import:

    import { createApi } from '@reduxjs/toolkit/query/react';
    

    From your todoAPI slice ensure you access and export the generated hooks.

    Example:

    import { createApi } from '@reduxjs/toolkit/query/react';
    
    export const todoApi = createApi({
      ...
      endpoints: builder => ({
        ...
        getTodos: builder.query({
          ...
        }),
        ...
      }),
    });
    
    export const {
      ...
      useGetTodosQuery,
      useLazyGetTodosQuery,
      ...
    } = todoApi;
    

    Import and use the generated hooks and now instead of manually selecting the query state from the store the hooks return that data directly (among all the related meta data, e.g. loading/fetching/success/error/etc).

    const { data } = useGetTodosQuery();
    // or
    const { data } = todoApi.useGetTodosQuery();
    
    const [trigger, { data }] = useLazyGetTodosQuery();
    //or
    const [trigger, { data }] = todoApi.useLazyGetTodosQuery();