Like react-query and redux? You'll love RTK Query
Tags: redux, react, reactquery
Published: 2022-12-07 01:05:16

React-query —now TanStack Query— is a library that helps you to fetch and manage data in your application. It's perfect for caching and handling data between components and its hooks make your code easy to read and understand.

However, there may be the case where your app uses —or will use— Redux, which IMO is the best option for handling complex state management. 

Actually with Redux, you can also fetch and store data in the state using thunks and specifically with ReduxToolKit with `createAsyncThunk` and handle the thunk [promise lifecycle actions](https://redux-toolkit.js.org/api/createAsyncThunk#promise-lifecycle-actions) in your slice.

Now, supposing that you already have RTK in your app and `createAsyncThunk` sounds like a lot of boilerplate and you also want a more simpler declarative _react-query-type_ strategy without having to also add react-query in the app?

This is when RTK Query comes into play! 

In TanStack Query you would usually fetch declaratively like so:
```jsx
function App() {
const { isLoading, error, data, isFetching } = useQuery(["repoData"], () =>
 axios
   .get("https://pokeapi.co/api/v2/pokemon/bulbasaur")
   .then((res) => res.data)
 );
```
We love this declarative way of fetching and caching data!

The great news here is that you can make use of a similar approach with RTK Query.

```jsx
export default function App() {
 const { data, error, isLoading } = useGetPokemonByNameQuery('bulbasaur')
```
But where is the inline function and the tag? Well, for that we'll use one single api where tags, requests, invalidations are handled. Why? From the authors:

> "Our perspective is that it's much easier to keep track of how requests, cache invalidation, and general app configuration behave when they're all in one central location in comparison to having X number of custom hooks in different files throughout your application."

The aforementhioned api should look like this:

```jsx
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
 
// services/pokemon.ts
export const pokemonApi = createApi({
 baseQuery: fetchBaseQuery({ baseUrl: 'https://pokeapi.co/api/v2/' }),
 tagTypes: [],
 endpoints: (builder) => ({
   getPokemonByName: builder.query({
     // name is the actual pokemon name - we used Bulbasaur in the example
     query: (name: string) => `pokemon/${name}`,
   }),
   getPokemonById: builder.query({
     query: (id: number) => `pokemon/${id}`
   }),
   // add more queries in the api
 }),
})
 
// Export hooks for usage in functional components
export const { useGetPokemonByNameQuery, useGetPokemonByIdQuery } = pokemonApi
```
As you may have noted, your queries are handled in your api and the hooks are also exported from it. Leaving you to choose where you want to import and use the hook.

I've added a `getPokemonById` query to the endpoints object to show you that here's where you are going to declare all your queries.

In my opinion this is great, it makes the hooks less verbose and your component code DRYer while still using the pattern that we love from TanStack Query.
As usual in Redux, with great power comes great responsibility which means some initial boilerplate. Just a bit.

You'll only have to add the middleware in your `configureStore` method and add an entry for the api in your `rootReducer`:

```jsx
import { pokemonApi } from './services/pokemon'
 
const rootReducer = combineReducers({
 [pokemonApi.reducerPath]: pokemonApi.reducer,
})
 
export const setupStore = (preloadedState?: PreloadedState<RootState>) => {
 return configureStore({
   reducer: rootReducer,
   middleware: (getDefaultMiddleware) =>
     // adding the api middleware enables caching, invalidation, polling and other features of `rtk-query`
     getDefaultMiddleware().concat(pokemonApi.middleware),
   preloadedState,
 })
}
```
Nothing changes in the way you wrap your app with RTK. So this should be done already

```jsx
import ReactDOM from 'react-dom/client'
import { Provider } from 'react-redux'
 
import App from './App'
import { setupStore } from './store'
 
const store = setupStore()
 
const reactRoot = ReactDOM.createRoot(
 document.getElementById('root') as HTMLElement
)
reactRoot.render(
 <Provider store={store}>
   <App />
 </Provider>)
```
Finally, the result will be the exactly same with both strategies:

![rtk vs rq](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nak6je0h05p465pkl4wz.png)

You can check out both implementations in [here](https://codesandbox.io/s/infallible-star-5kg86k?file=/src/TanstackRQ.tsx).

**Bonus - Data Invalidation**

And the data invalidation? You've probably used a mutation with `useMutation` —or even with `useQuery`— and you want the data to be invalidated after this request and thus the cache removed and the data refetched. 

Supposing that we are fetching several posts from the DB. We'd do it this way

```jsx
export const api = createApi({
 baseQuery: fetchBaseQuery({ baseUrl: '/' }),
 tagTypes: ['Posts'],
 endpoints: (build) => ({
   getPosts: build.query<Post[], void>({
     query: () => 'posts',
     providesTags: ['Posts'],
   }),
   addPost: build.mutation<Post, Partial<Post>>({
     query: (body) => ({
       url: `post`,
       method: 'POST',
       body,
     }),
     invalidatesTags: ['Posts'],
   }),
```

1. Adding the `tagTypes` key with an array of the request tags, that later will be linked with our requests to create a _relationship_.
2. Then a `providesTags` array that accepts a string or a callback. We'll use a string for the moment which would invalidate the entire request.

And in the component where we invoke the mutation:

```jsx
 // in YourForm.tsx
   const [addPost] = useAddPostMutation()
 
   const handleAddPost (body) => {
     addPost(body);
   }
 
   return (
     <form onSubmit={handleAddPost}>
       // your form
     </form>
   )
```
Once your mutation request has succeeded any other request "linked" with the `providesTag: ['Posts']` will have its cache invalidated, hence the data won't be served immediately but it would be refetched to have the data updated.

You can even avoid refetching the entire request by using the `providesTag` callback similar to how [it's done](https://tanstack.com/query/v4/docs/guides/query-invalidation) in TanStack Query. If you are interested in a more advanced usage, [the docs](https://redux-toolkit.js.org/rtk-query/usage/automated-refetching) are very well documented.

**Conclusion**

**Redux** might not be the optimal solution for all the apps, most of the time **react-query**, **Zustand**, **Jotai** or —god forbid— even **Context** are enough for handling and storing states, however, for those who enjoy —or need to use— Redux will not be disappointed with the huge array of tools that **RTK** provides to the developers to make their experience with Redux frictionless and in my case, enjoyable.

Foto from <a href="https://unsplash.com/@oskark?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Oskar Kadaksoo</a> in <a href="https://unsplash.com/es/s/fotos/fetch-dog?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Unsplash</a>