In this guide, I will show you how to set up Redux Toolkit and RTK Query with React and TypeScript the right way.
Related articles:
- React, Material UI and React Hook Form: Login and Signup Forms
- React, RTK Query, React Hook Form and Material UI – Image Upload
- React + Redux Toolkit: JWT Authentication and Authorization
- React.js + Redux Toolkit: Refresh Tokens Authentication
- React CRUD example with Redux Toolkit, RTK Query & REST API

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.
Technology Stack
- React
- TypeScript
- Redux Toolkit
- React-redux
- RTK Query
Prerequisites
- Comfortable with ES6 syntax and features
- Familiarity with React core concepts: JSX, Props, State, Functional Components
- Comfortable with Redux and its terminologies
How to Read this Tutorial Guide
This tutorial will focus on how to set up Redux Toolkit and RTK Query with React. I will assume you already have a good understanding of Redux and how to manage state with it in a React app.
For a more detailed explanation of what Redux is, how it works, and demos on how to use Redux Toolkit, check out the Redux overview tutorial.
The examples will be based on a typical Create React App project where all the code will be in the src folder. Also, I will provide some best practices to adopt when using Redux Toolkit with React.
The recommended way to Add Redux Toolkit to React
The recommended way to initialize a new app with React and Redux is by using the official Redux+JS template or Redux+TS template.
Creating a React app that uses Redux this way is a lot quicker and also prevents you from making mistakes.
# Redux + Plain JS template
npx create-react-app my-app --template redux
# Redux + TypeScript template
npx create-react-app my-app --template redux-typescript
Add Redux Toolkit to an Old React Project
This method is for those who want to add Redux Toolkit to their old React Projects.
If you also want to learn how to set up Redux Toolkit and RTK Query with React from scratch to understand the ins and outs of Redux Toolkit then you are at the right place.
When starting a new React project with Redux, I recommend you follow the recommended way to add Redux Toolkit to React since it’s quicker and easier to set up.
Am going to use Yarn as my package manager for this tutorial, you can use NPM if you are more comfortable with it. The package manager you use doesn’t affect the code we will write.
Initialize a New React App
Before we start fetching and installing the required dependencies, let’s first initialize a new React App if you don’t have one.
Run this command to create a new React app with TypeScript.
# NPM
npx create-react-app redux-app --template typescript
# Yarn
yarn create react-app redux-app --template typescript
The project initialization process will take a couple of minutes depending on your internet speed so sit back and grab some coffee while Create React App does its job in the background.
Install Redux Toolkit and React-Redux
Fetch and install Redux Toolkit and React-redux in the project.
# NPM
npm install @reduxjs/toolkit react-redux
# Yarn
yarn add @reduxjs/toolkit react-redux
Redux Toolkit is already written in Typescript so we don’t need to worry about installing its type definition files separately.
React redux has a dependency on @types/react-redux so the type definition file of the package will be automatically installed with it.
Create a Redux Store
Inside the src folder, create a redux folder and within this redux folder create a store.ts
file.
Now your folder structure should look somewhat like this.
redux-app/ ├── node_modules/ ├── public/ │ ├── favicon.ico │ ├── index.html │ ├── logo192.png │ ├── logo512.png │ ├── manifest.json │ └── robots.txt ├── src/ │ ├── redux/ │ │ └── store.ts │ ├── App.css │ ├── App.test.tsx │ ├── App.tsx │ ├── index.css │ ├── index.tsx │ ├── logo.svg │ ├── react-app-env.d.ts │ ├── reportWebVitals.ts │ └── setupTests.ts ├── .gitignore ├── package.json ├── README.md ├── tsconfig.json └── yarn.lock
To create a store in Redux Toolkit, we have to use the configureStore API which is a standard abstraction over the createStore
function but adds some good default configurations for a better development experience.
The configureStore
function accepts a single configuration object with the following properties:
- reducer
- devTools
- middleware
- enhancers
- preloadedState
We are going to focus on the three essential properties (reducer, devTools and middleware) to configure the store.
import { configureStore } from '@reduxjs/toolkit'
export const store = configureStore({
reducer: {}
})
We don’t need to provide the configureStore
with any additional typings.
Define the Root State and Dispatch Types
We need to extract the RootState and AppDispatch from the store and export them directly from the store.ts
file.
Inferring RootState and AppDispatch from the store itself means that they’ll correctly update as you add more 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
Provide the Redux Store to the React App
Since the store has been created, we need to provide it to all our components from the top level of our application.
In the index.tsx
file, import the store from ./redux/store
and the <Provider>
component from react-redux.
Wrap the Provider component around the app component and pass the store as a prop to the Provider.
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>
);
Define the State Selector and Dispatch Typed Hooks
While it’s possible to import the RootState
and AppDispatch
types we defined inside the store.ts
file into each component, it’s recommended to create typed versions of the useDispatch
and useSelector
hooks for usage in your entire application. Below are some of the reasons why you shouldn’t import the RootState or AppDispatch types into each component.
- For
useSelector
hook, it saves you the need to type(state: RootState)
whenever you want to access the state. - For
useDispatch
hook, we need to use the customized AppDispatch type we defined in thestore.ts
file to dispatch thunks correctly since the default Dispatch type doesn’t know anything about thunks.
The middleware types are also included in the AppDispatch type. Using a pre-typed useDispatch will prevent you from repeatedly importing the AppDispatch every time you need to dispatch an action.
Within the redux folder ./src/redux
create a new hooks.ts
file and add the code snippets below.
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
Define the typed useDispatch
and useSelector
hooks in a separate file instead of the store.ts
file. This will allow you to import them into any component that needs to make use of hooks and prevent you from falling into the circular import dependency issues.
Create a Redux State Slice and Action Types
Now it’s time to create our first redux state slice, inside the redux folder create a features directory to house all our slices.
Next, create a new file named src/redux/features/products/authSlice.ts
.
copy and paste the code snippets below into 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;
If you’ve used Redux in the past you know Redux requires us to write all state updates immutably by making copies of the state and updating the copies.
In Redux Toolkit we have the luxury to mutate any state directly.
Redux Toolkits createSlice
and createReducer
APIs use Immer under the hood to allow us to write “mutating” update logic that eventually becomes immutable updates.
Add the Reducer of the Redux State Slice to the Store
Next, we need to import the reducer function we exported from the authSlice.ts into the store and add it to the reducers object.
By defining the authUser
field inside the reducer
object, we tell the store to use the authReducer
function to handle all 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;
Add RTK Query to an Old React Project
RTK Query is an advanced data fetching and caching tool, designed to fetch and cache API data.
RTK Query is built on top of the Redux Toolkit core and uses Redux Toolkit’s APIs like createSlice and createAsyncThunk for its implementation.
It is not mandatory to use RTK Query with Redux Toolkit but it’s recommended to combine both Redux Toolkit and RTK Query in your project if you will be managing both local state and API data.
You can use other API state management tools like React Query but getting it up and running with Redux Toolkit is a pain in the ass.
That is why the Redux team decided to create RTK Query which looks similar to React Query but it’s more compatible and easier to use with Redux Toolkit.
Create an API Service
Create an api
folder within the redux directory and create a file named src/redux/api/products/productAPI.ts
.
Below is a CRUD operation with RTK Query where am fetching all the products, getting a single product, updating a single product and deleting a single product.
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;
createAPI
accepts a single configuration object which has some of the following parameters:
- reducerPath: used as the root state key when adding the reducer function to the store.
- baseQuery: uses fetchBaseQuery which is a small wrapper around the fetch API
- endpoints: a function that returns an object containing all the API endpoint logics.
Add the API Service to the Redux Store
RTK Query will generate a “slice reducer” from the productAPI
that should be added to the Redux root reducer.
A custom middleware will also be generated and we need to add it to the middleware parameter.
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;
Adding the API middleware enables caching, polling, invalidation, and other essential features on RTK Query.
Conclusion
In this mini crash course, we looked at how to set up Redux Toolkit and RTK Query with React and TypeScript.
You can also read:
Hello, Thank you for the tutorial. However, you did not show us how to pass RTK query response to RTK store. and how to access it in react components using useSelector.
Thank you.
Thanks for your comment. This article only demonstrates how RTK Query can be integrated into a React.js project. The related articles go in-depth about how RTK Query can be used to handle API requests and responses. Nevertheless, I will allocate time to add the section that demonstrates how it can be used with the useSelector hook in a React component.
I hope the best for you life. <3
thankks
Nice article but you are setting up createApi incorrectly, the docs say you should only use it once per endpoint. Your folder structure implies multiple uses https://redux-toolkit.js.org/rtk-query/api/createApi
Multiple API slices can be created for an endpoint that needs separation of concerns.
If the project is small then you can create only one API slice for the API endpoint but the moment you decide to use different tagTypes, reducerPath, and baseQuery then it will make a lot of sense to create different API slices.