How to use RTK Query in a React Application

ยท

7 min read

Data fetching is an essential part of building applications. Most apps require data from numerous sources to display information or conduct actions. Data fetching is critical when developing scalable and maintainable programs. By retrieving data from an external source and caching it in the application, the program can avoid making unnecessary requests and reduce the load on the server. This enhances the overall performance of the application and delivers a better user experience.

In this article, we will learn about RTK Query, a data fetching and caching library from Redux. We will also learn why we probably want to use it in our next application and the numerous alternatives available.

Without further ado, let's get started.

What is RTK Query?

The Redux Toolkit Query, commonly known as RTK Query, is a Redux-integrated data fetching and caching module. It offers a simple and efficient method of managing data from numerous sources. A REST API would be a good example. Because RTK Query employs normalized caching, you can be confident that the data in your store is always up-to-date and consistent.

Normalized caching does this by dividing data into smaller, more manageable chunks and storing them in distinct areas, making it easier to retrieve and handle the data.

Disclaimer โš : For this tutorial, we assume you already have an idea of what Redux is and how it works

Getting Started

Run the following command to setup a Redux + React boilerplate:

# Redux + Plain JS template
npx create-react-app my-app --template redux

# Redux + TypeScript template
npx create-react-app my-app --template redux-typescript

Once complete, you will have a React application with Redux already integrated.

Creating our API slice

We are going to head over to our src\features folder and create a file called apiSlice. Feel free to name the file whatever you want if you feel like it.

# apiSlice.js
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";

export const apiSlice = createApi({
    reducerPath: 'apiSlice',
    baseQuery: fetchBaseQuery({ baseUrl: 'https://jsonplaceholder.typicode.com/' }),
    endpoints: (builder) => ({
        getPosts: builder.query({
            query: () => `posts`
        })
    }),

})


export const { useGetPostsQuery } = apiSlice

From the code above, we first import createApi and fetchBaseQuery from the @reduxjs/toolkit/query/react library.

We then use the createApi function to define an API slice with the following configuration options:

  • The reducerPath option specifies the name of the slice's reducer.

  • The baseQuery option specifies the base function that will be used to make requests to the server. In this case, it's using the fetchBaseQuery function from the @reduxjs/toolkit/query library to set the baseUrl option to https://jsonplaceholder.typicode.com/. This means that all requests made will use this as their base URL.

  • The endpoints option is a function that takes a builder object as an argument. The builder object provides a set of functions to define query, mutation, and subscription endpoints for the API slice.

As a rule of thumb, use the builder.query function in RTK Query if all you want to do is get data from an endpoint.

Use the builder.mutation function in RTK Query when you need to perform a server-side mutation that modifies data on the server, such as creating, updating, or deleting resources. This would typically involve making a POST, PATCH or DELETE request to a server endpoint with the user's information in the request body.

We then define a getPosts endpoint that simply returns the posts path from the server's API. It does this by querying our baseURL and the additional path we provided. In our case, we provided an additional path of posts. Hence, the total URL looks something like `https://jsonplaceholder.typicode.com/posts`.

The createApi function generates a set of hooks based on the configuration of the slice. For every builder.query endpoint we create, a new hook is created for use that prependsuse and appends Query to the endpoint's name. In this case, the useGetPostsQuery hook is generated, because the endpoint's name is getPosts.

๐Ÿ’ก Tip: Typically, you should only have one API slice per base URL that your application needs to communicate with.

Configuring our store

Now, let's head over to our app/store file. To configure RTK Query to work with our store, paste the following code below into your store.

import { configureStore } from '@reduxjs/toolkit'
// Or from '@reduxjs/toolkit/query/react'
import { apiSlice} from '../features/apiSlice'

export const store = configureStore({
  reducer: {
     [apiSlice.reducerPath]: apiSlice.reducer,
  },

  middleware: (getDefaultMiddleware) =>
   return getDefaultMiddleware().concat(apiSlice.middleware);
})

From the above code, we import our apiSlice. Next, we add apiSlice.reducerPath property to our reducers object. The apiSlice.reducerPath value is then set to the apiSlice.reducer function, which is the reducer function generated by RTK Query

Then, we set up the middleware specifically for RTK Query. The middleware function first calls getDefaultMiddleware() to retrieve the default middleware provided by Redux. It then uses the concat method to add apiSlice.middleware to the end of this array. The apiSlice.middleware property contains the middleware generated by RTK Query.

Using RTK Query to fetch data

Next, we are going to head over to our src/Appfile and paste the following code

import React from 'react'
import { useGetPostsQuery } from './features/apiSlice'

function App() {
  const { data, isLoading } = useGetPostsQuery("");

  if (isLoading) {
    return <p>is loading...</p>
  }

  return (
    <div>
      {data?.map((post: any) => (
        <p>{post?.title}</p>
      ))}
    </div>
  )
}

export default App

From the code above, we import the useGetPostsQuery hook from our apiSlice file. We then de-structure data and isLoading from useGetPostsQuery. Data is the actual data returned from the API. While isLoading is a boolean that checks if the endpoint is still trying to get our data.

RTK Query also provides other ways we can track the state. Such asstatus, error and utility booleans like isLoading, isFetching, isSuccess, and isError.

Bonus: Mutating data

Apart from fetching data from our API, let's try to add a new post. We are going back to our src\features\apiSlice file and add the following code

export const apiSlice = createApi({
    ...
    endpoints: (builder) => ({
        ...
        addPost: builder.mutation({
            query: ({ ...rest }) => ({
                url: '/posts',
                method: 'POST',
                body: rest,
            }),
        })
    }),
})

export const { ..., useAddPostMutation } = apiSlice

As you can recall, we said that the builder.mutation function is used when we want to modify data on the server. Hence, that's why we used it above. We then pass the following configuration options to the builder.mutation function.

The query option is a function that takes an object as its argument, which contains any data that needs to be sent to the server. In this case, the object is de-structured using the spread operator ({ ...rest }) to include any additional fields that may be passed to the query.

The query function then returns an object with the following properties:

  • The url property specifies the URL that the request should be sent to. In this case, it's set to '/posts', which means that the request will be sent to the https://jsonplaceholder.typicode.com/posts endpoint on the server.

  • The method property specifies the HTTP method that should be used to send the request. In this case, it's set to POST, which means that the request will be a POST request.

  • The body property specifies the data that should be sent in the request body. In this case, it's set to the rest object, which contains any additional data that was passed to the query.

Whenever we use the builder.mutation function to create an endpoint, a hook gets created for us. The naming convention of the hook is as follows: use gets prepended to our endpoint name. While Mutation gets appended to the name. In essence, since our endpoint's name is addPost, the hook generated will be useAddPostMutation.

Now, we are going to head over to our src\Appfile and paste the following code

import React from 'react'
import { useAddPostMutation } from './features/apiSlice'

function App() {
  const [addPost] = useAddPostMutation();

  const addNewPost = () => {
    addPost({
      userId: 80,
      id: 80,
      title: "Test title",
      body: "Test body"

    }).then((res) => {
      console.log(res);
    })
  }

  return (
    <div>
      <button onClick={addNewPost}>Add post</button>
    </div>
  )
}

export default App

From the above code, we import the useAddPostMutation hook from the ./features/apiSlice file.

We then use the useAddPostMutation hook to create a addPost mutation function.

Finally, we add a button that, when clicked, will call the addNewPost function to initiate the addPost mutation.