Pages

How to Use RTK Queries in a React App?

Before we get into the technicality of using RTK Query in place of React query, let's understand the basics and know what actually RTK Query is.


RTK Query is nothing but an advanced tool to fetch data on the client side. It is basically a caching tool and it works similarly to React. The only good thing about RTK Query is that it directly integrates with Redux. When interacting with APIs, developers frequently use Redux with middleware modules like Thunk.


Adding RTK Query to Redux Toolkit is not mandatory, but when you combine both of them in a React project, it brings out the true power of Redux Toolkit. However, it's advisable to use the official Redux+JS template or the Redux+TS template to initialize a new app with React and Redux.


To help you learn, we've created a React App project with all of the code in the src folder. We will provide some best practices to follow when utilizing Redux Toolkit with React in addition to instructing you on how to use RTK queries in React apps.


Install the React-Rtk package:

To get it started, let's start with using the official Redux+JS template or Redux+TS template. It is way easier and faster to create a React app that uses Redux. This also prevents you from making mistakes.


Here are the complete steps to add Redux Toolkit to an old React project:

There are a thousand ways to learn React programming, but if you want to learn it from scratch, then you are on the right platform. Here are detailed instructions for configuring Redux Toolkit and RTK Query with React. You will be able to comprehend the intricacies of the Redux Toolkit.


Let's start with an NPM because most developers are familiar with it. You can also use any other package manager according to availability, as it will not affect the written code.


  1. Launch a New React App

Let's first initialize a new React app if you don't already have one before we begin fetching and installing the necessary dependencies.


Use this command to launch a new TypeScript-enabled React app.



# Redux + Plain JS template

npx create-react-app my-app --template redux


# Redux + TypeScript template

npx create-react-app my-app --template redux-typescript



The project will take a few minutes to get initiated.


  1. Install React-Redux and Redux Toolkit

First of all, you have to install Redux Toolkit and React-redux within the project.


As Redux Toolkit is already installed in the project in Typescript format so we only have to install react-Redux using the below command:



# NPM

npm install @reduxjs/toolkit react-redux

# Yarn

yarn add @reduxjs/toolkit react-redux



React Redux has a dependency on @types/react-redux so the type definition file of the package will be automatically installed with it.


  1. Create Redux Store

Create a redux folder inside the src folder, and within this redux folder, create a store.ts file.


Redux Toolkit is already written in Typescript, so we don’t need to worry about installing its type definition files separately.



import { configureStore } from '@reduxjs/toolkit'


export const store = configureStore({

  reducer: {}

})




There is no need to include configureStore in addition. 


  1. Define Root State

Extract RootState and AppDispatch  and export it directly from the store.ts file.


RootState and AppDispatch inferred from the store will update as you add state slices, API services, or modify middleware settings. 



import { configureStore } from '@reduxjs/toolkit'


export const store = configureStore({

  reducer: {}

})


// Infer the `RootState` and `AppDispatch` types from the store itself

export type RootState = ReturnType<typeof store.getstate>

export type AppDispatch = typeof store.dispatch



  1. Insert Redux Store 

Since the store is created, it must be provided to all application components.


Import ./redux/store and the <Provider> component from react-redux to the index.tsx file.


Wrap the app component in the provider component, then give the provider the store as a prop.



import React from 'react';

import ReactDOM from 'react-dom/client';

import './index.css';

import App from './App';

import reportWebVitals from './reportWebVitals';

// ? Import Provider from react-redux and store from ./redux/store

import { Provider } from 'react-redux';

import { store } from './redux/store';


const root = ReactDOM.createRoot(

  document.getElementById('root') as HTMLElement

);

root.render(

  <React.StrictMode>

    {/* ? Provide the store as prop */}

    <Provider store={store}>

      <App />

    </Provider>

  </React.StrictMode>

);



  1. Define the State Selector

The RootState and AppDispatch types that we defined in the store.ts file can be imported into each component, however, it is advised to create typed versions of the useDispatch and useSelector hooks to be used throughout your entire application. 


  1. Dispatch Typed Hooks

Create a new hooks.ts file within the redux folder ./src/redux.


Write the below code to add the snippets.



import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';

import type { RootState, AppDispatch } from './store';


// Use throughout your app instead of plain `useDispatch` and `useSelector`

export const useAppDispatch = () => useDispatch<appdispatch>();

export const useAppSelector: TypedUseSelectorHook<rootstate> = useSelector



  1. Create a Redux State Slice

After defining Hooks and State Selector now it’s time to create our first redux state slice.


Create a features directory Inside the redux folder to house all our slices.


Create a new file with the name: src/redux/features/products/authSlice.ts.


Copy and paste the below code in the authSlice.ts file.


import { createSlice, PayloadAction } from '@reduxjs/toolkit';


export interface IUser {

  _id: string;

  name: string;

  email: string;

  photo: string;

  role: string;

  provider?: string;

  active?: boolean;

  verified?: boolean;

  createdAt: Date;

  updatedAt: Date;

  __v: number;

  id: string;

}


interface AuthState {

  user?: IUser | null;

}


const initialState: AuthState = {

  user: null,

};


export const authSlice = createSlice({

  name: 'authSlice',

  initialState,

  reducers: {

    // ? Logout the user by returning the initial state

    logout: () => initialState,

    // Save the user's info

    userInfo: (state, action: PayloadAction<authstate>) => {

      // Redux Toolkit allows us to write "mutating" logic in reducers. It

      // doesn't actually mutate the state because it uses the Immer library,

      // which detects changes to a "draft state" and produces a brand new

      // immutable state based off those changes

      state.user = action.payload.user;

    },

  },

});


export const { logout, userInfo } = authSlice.actions;

// ? Export the authSlice.reducer to be included in the store.

export default authSlice.reducer;




Those who have used Redux code in the past must know that it requires mentioning all state updates immutably by making copies of the state.


Redux Toolkit gives us the luxury of directly mutating any state.


  1. Add Reducer of the Redux State Slice

In the next step, we will import the reducer function.

To do this, we will export the authSlice.ts into the store and add it to the reducers object.


By defining the authUser field inside of the reducer object, we can communicate to the store that the authReducer function should be used to manage any updates to that state.


import { configureStore } from '@reduxjs/toolkit';

// ? import authReducer from authSlice

import authReducer from './features/authSlice'


export const store = configureStore({

  reducer: {

    // ? Add the authReducer to the reducer object

    authUser: authReducer

  },

// ? show the devTools only in development

devTools: process.env.NODE_ENV !== 'production',

});


export type RootState = ReturnType<typeof store.getstate>;

export type AppDispatch = typeof store.dispatch;




  1. Add RTK Query

RTK Query will help us retrieve API data from the cache. It is designed on top of the Redux Toolkit and uses its APIs for implementation.


It is not required to use RTK Query with Redux Toolkit; however, it is highly recommended to combine both Redux Toolkit and RTK Query in your project if you will be managing both local state and API data. While it is not required to use RTK Query with Redux Toolkit, it is highly recommended to do so. 


You are free to make use of other API state management tools such as React Query; however, using Redux Toolkit to get it set up and operational can be a real pain in the rear.


Because of this, the Redux team came to the conclusion that it would be beneficial to develop RTK Query, which has a user interface that is analogous to that of React Query but is more compatible with Redux Toolkit and is less complicated to operate.


  1. Create an API Service

It is necessary to create an API service within the redux directory. 

Create an API file named src/redux/api/products/productAPI.ts


An example of a CRUD operation performed with RTK Query is shown below. In this example, I am retrieving all of the products, obtaining a single product, updating a single product, and deleting a single product.


createAPI will take a single configuration object that may have any of the following parameters if it is provided.


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


type IProduct = {

  _id: string;

  name: string;

  avgRating: number;

  numRating: number;

  price: number;

  description: string;

  countInStock: number;

  quantity?: number;

  imageCover: string;

  images: string[];

  category: string;

  createdAt: Date;

  updatedAt: Date;

  slug: string;

  id: string;

};


export const productApi = createApi({

  reducerPath: 'productApi',

  baseQuery: fetchBaseQuery({ baseUrl: 'http://localhost:8000/api/' }),

  tagTypes: ['Products'],

  endpoints: (builder) => ({

    // ? Query: Get All Products

    getProducts: builder.query<iproduct[], void>({

      query() {

        return 'products';

      },

      providesTags: (result) =>

        result

          ? [

              ...result.map(({ id }) => ({

                type: 'Products' as const,

                id,

              })),

              { type: 'Products', id: 'LIST' },

            ]

          : [{ type: 'Products', id: 'LIST' }],

      // ? Transform the result to prevent nested data

      transformResponse: (response: { data: { products: IProduct[] } }) =>

        response.data.products,

    }),

    // ? Query: Get a single product

    getProduct: builder.query<iproduct, string>({

      query(id) {

        return `products/${id}`;

      },

      transformResponse: (

        response: { data: { product: IProduct } },

        args,

        meta

      ) => response.data.product,

      providesTags: (result, error, id) => [{ type: 'Products', id }],

    }),

    // ? Mutation: Create a product

    createProduct: builder.mutation<iproduct, formdata>({

      query(data) {

        return {

          url: 'products',

          method: 'POST',

          credentials: 'include',

          body: data,

        };

      },

      invalidatesTags: [{ type: 'Products', id: 'LIST' }],

      transformResponse: (response: { data: { product: IProduct } }) =>

        response.data.product,

    }),

    // ? Mutation: Update Product

    updateProduct: builder.mutation<

      IProduct,

      { id: string; formData: FormData }

    >({

      query({ id, formData }) {

        return {

          url: `products/${id}`,

          method: 'PATCH',

          credentials: 'include',

          body: formData,

        };

      },

      invalidatesTags: (result, error, { id }) =>

        result

          ? [

              { type: 'Products', id },

              { type: 'Products', id: 'LIST' },

            ]

          : [{ type: 'Products', id: 'LIST' }],

      transformResponse: (response: { data: { product: IProduct } }) =>

        response.data.product,

    }),

    // ? Mutation: Delete product

    deleteProduct: builder.mutation<null, string>({

      query(id) {

        return {

          url: `products/${id}`,

          method: 'DELETE',

          credentials: 'include',

        };

      },

      invalidatesTags: [{ type: 'Products', id: 'LIST' }],

    }),

  }),

});


export const {

  useCreateProductMutation,

  useUpdateProductMutation,

  useDeleteProductMutation,

  useGetProductsQuery,

  useGetProductQuery,

  usePrefetch,

} = productsApi;



  1. Adding the final API Service to the Redux Store

RTK Query will produce what is known as a "slice reducer" from the productAPI, which is intended to be added to the Redux root reducer.


Additionally, a specialised middleware will be generated, and the middleware parameter will need to be updated to include it.


import { configureStore } from '@reduxjs/toolkit';

// ? import authReducer from authSlice

import authReducer from './features/authSlice';

import { productsApi } from './api/products/productAPI';


export const store = configureStore({

  reducer: {

    // ? Add the authReducer to the reducer object

    authUser: authReducer,

    [productsApi.reducerPath]: productsApi.reducer,

  },

  devTools: process.env.NODE_ENV !== 'production',

   // Adding the api middleware enables caching, invalidation, polling,

   // and other useful features of `rtk-query`.

  middleware: (getDefaultMiddleware) =>

    getDefaultMiddleware({}).concat([productsApi.middleware]),

});


export type RootState = ReturnType<typeof store.getstate>;

export type AppDispatch = typeof store.dispatch;



Final thoughts:

RTK Query implementation in a React app is critical for creating efficient and powerful apps. Developers can make applications that work well and are easy to maintain by using the query syntax and providing the necessary data. Even though the process can be difficult, the end result has many benefits and can help developers make apps with more features that can be used for a wide range of tasks. With the right guidance, developers can easily and effectively implement RTK Query in a React app and take their applications to the next level.


No comments:

Post a Comment

Make new Model/Controller/Migration in Laravel

  In this article, we have included steps to create a model and controller or resource controller with the help of command line(CLI). Here w...