paint-brush
The RTK Query, API Splitting Approach, and the Nuances of "Making Life Simpler"by@anatolii
4,210 reads
4,210 reads

The RTK Query, API Splitting Approach, and the Nuances of "Making Life Simpler"

by Anatolii KabanovDecember 31st, 2021
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

According to the Redux Toolkit, “RTK Query is a powerful data fetching and caching tool. It is designed to simplify common cases for loading data in a web application, eliminating the need to hand-write data fetching & caching logic yourself.” With the help of RTK Query, you can significantly decrease the number of actions, reducers, and side effects. Right out of the box, there is the possibility to query every N seconds, auto cache, and send repeated requests in the case when cache became invalid. It’s definitely a cool library however it’s sadly not applicable to all of the use cases possible with an API. This is because it’s managing both the state and all mutations.
featured image - The RTK Query, API Splitting Approach, and the Nuances of "Making Life Simpler"
Anatolii Kabanov HackerNoon profile picture


According to the Redux Toolkit, “RTK Query is a powerful data fetching and caching tool. It is designed to simplify common cases for loading data in a web application, eliminating the need to hand-write data fetching & caching logic yourself.”


With the help of RTK Query, you can significantly decrease the number of actions, reducers, and side effects.


Right out of the box, there is the possibility to query every N seconds, auto cache, and send repeated requests in the case when cache became invalid.


It’s definitely a cool library however it’s sadly not applicable to all of the use cases possible with an API. This is because it’s managing both the state and all mutations.


For example, I once tried to use it to query some table data and change it -- in other words, basic CRUD operations. I was faced, however, with an issue when I wanted to add additional data to objects and update that data without calling the API.


For example, if I want to handle a few basic requests via RTK Query and not include manage state, or to not write reducers logic, etc.


For better code readability, I realized it’s possible to use the API splitting approach and created baseApi:


export const baseApi = createApi({
    reducerPath: 'baseApi',
    tagTypes: ['Notifications'],
    baseQuery: fetchBaseQuery({ baseUrl: getBaseUrl() }),
    endpoints: () => ({}),
});


For the API, which I need to query, I then wrote notificationsApi:


const notificationsApi = baseApi.injectEndpoints({
    endpoints: build => ({
        fetch: build.query<INotification[], INotificationBaseRequest>({
            query: buildUrl,
            transformResponse: (x: IResponse<INotification[]>) => x.result,
            providesTags: ['Notifications'],
        }),
        remove: build.mutation<void, IRemoveNotificationRequest>({
            query: arg => ({
                url: buildUrl(arg),
                method: 'DELETE',
            }),
            invalidatesTags: (_: any, id: any) => [{ type: 'Notifications', id }],
        }),
        removeAll: build.mutation<void, INotificationBaseRequest>({
            query: arg => ({
                url: buildUrl(arg),
                method: 'DELETE',
            }),
            invalidatesTags: ['Notifications'],
        }),
    }),
});

export const { useFetchQuery, useRemoveMutation, useRemoveAllMutation } = notificationsApi;


Where buildUrl is a simple function that combines URL arguments in one string, such as notification ID to ‘baseUrl/notifications/{id}’.


In the endpoints section I introduced three operations that I needed to use.


The fetch operation retrieves data by GET request.


The data looks like an array:


[ { id: number; name: string; message: string; }, ... ]


This array I can easily get in component via the useFecthQuery hook.


const { data, isLoading } = useFetchQuery({ ...params });


Here, I don’t need to implement isLoading logic that actually describes the status of the HTTP request.


It is already there in the hook.


However, please be careful, the isLoading is for the first query only.


For common casesisFetching is more appropriate. The hook will automatically request data (in case if there is no data or the cache is invalid). More about cache.


The remove operation is used to send a DELETE request and remove one notification.


The invalidatesTags is there to remove only one entry from the array by id.


 const [remove] = useRemoveMutation();


Use it without dispatch, just as it is.


You can call remove on a button click or another user action. As well as the removeAll operation.


const [removeAll, { isLoading: isDeleting }] = useRemoveAllMutation();


Also in such an operation, there is a possibility to use properties to understand the status of HTTP requests. The main difference between remove and removeAll is that in removeAll I remove the whole cache.


That’s pretty much it, all data stored, actions automatically generated. No need to manage it manually… or so I thought.


But I understood that I want to extend objects in the array, to have an additional property: isCollapsed. It can not be done easily - just changing the current state of cached objects.


The RTK Query manages the state by itself and I can’t create another action to handle such behavior. However, I found two ways how to solve it!


First of all, I could create my own state NotificationState, where there will be additional data stored, of course, I need to createSlice or createReducer and add more actions.


Or I will not use RTK Query, and all will be done via createSlice where objects will be extended with the required fields.


The general takeaway here is, if for some reason you need to extend objects, or in your API, objects come in different schemas and you want to store it all in one place, it is better to manage the state by yourself from the begining.