State management is a core part of building large, scalable React applications. Redux is a widely-used state management library for React, and the Redux Toolkit (RTK) provides a more efficient, concise, and user-friendly approach to using Redux. In this chapter, we'll go from basic concepts to advanced techniques, exploring how Redux Toolkit simplifies state management in React applications.
State management refers to how you handle the data in your application—how the state is stored, updated, and shared between components. In small React apps, state can be managed using local component state, but as your app grows, managing state between multiple components can become complex.
Redux allows you to manage state outside your components, in a global store. This provides a predictable, centralized state container. The traditional Redux approach, however, involves writing a lot of boilerplate code, which can be tedious. This is where Redux Toolkit comes in.
Redux Toolkit (RTK) is the official, recommended way to write Redux logic. It simplifies common tasks and reduces boilerplate code, making Redux more approachable for new developers while keeping it powerful for advanced use cases.
createSlice
for reducers and createAsyncThunk
for async actions.redux-thunk
.To use Redux Toolkit, install it in your project along with React-Redux:
npm install @reduxjs/toolkit react-redux
Next, set up your store and provide it to the app:
// store.js
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './counterSlice';
export const store = configureStore({
reducer: {
counter: counterReducer,
},
});
// index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import { Provider } from 'react-redux';
import { store } from './store';
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
);
Here, configureStore
sets up your Redux store, and Provider
wraps your React app to give it access to the store.
A slice is a collection of Redux reducer logic and actions for a single feature of your app. Each slice manages a part of the state.
// counterSlice.js
import { createSlice } from '@reduxjs/toolkit';
const initialState = {
value: 0,
};
export const counterSlice = createSlice({
name: 'counter',
initialState,
reducers: {
increment: (state) => {
state.value += 1;
},
decrement: (state) => {
state.value -= 1;
},
incrementByAmount: (state, action) => {
state.value += action.payload;
},
},
});
export const { increment, decrement, incrementByAmount } = counterSlice.actions;
export default counterSlice.reducer;
In this example, createSlice
automatically generates action creators and a reducer function based on the reducers defined in the slice.
The store is where the global state lives. You configure the store using configureStore
from Redux Toolkit.
A reducer is a pure function that takes the current state and an action as inputs, and returns the next state. In Redux Toolkit, reducers are automatically generated when you create a slice.
Actions are payloads of information that send data from your application to your Redux store. Redux Toolkit generates action creators for each reducer function.
In Redux Toolkit, createAsyncThunk
is used to handle asynchronous logic, such as API calls.
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';
import axios from 'axios';
// Define an async thunk
export const fetchUserData = createAsyncThunk(
'user/fetchUserData',
async (userId, thunkAPI) => {
const response = await axios.get(`/api/user/${userId}`);
return response.data;
}
);
const userSlice = createSlice({
name: 'user',
initialState: { data: null, status: 'idle' },
reducers: {},
extraReducers: (builder) => {
builder
.addCase(fetchUserData.pending, (state) => {
state.status = 'loading';
})
.addCase(fetchUserData.fulfilled, (state, action) => {
state.status = 'succeeded';
state.data = action.payload;
})
.addCase(fetchUserData.rejected, (state) => {
state.status = 'failed';
});
},
});
export default userSlice.reducer;
Let’s create a simple counter app that uses Redux Toolkit for state management:
// Counter.js
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { increment, decrement, incrementByAmount } from './counterSlice';
const Counter = () => {
const count = useSelector((state) => state.counter.value);
const dispatch = useDispatch();
return (
{count}
);
};
export default Counter;
In this example, useSelector
retrieves the state from the store, and useDispatch
dispatches actions to update the state.
To manage complex, nested data, you can normalize your state using Redux Toolkit’s createEntityAdapter
.
Redux Toolkit allows you to easily set up custom middleware. Additionally, RTK Query simplifies fetching and caching data by providing a set of tools for data fetching, caching, and synchronization with the server.
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
export const apiSlice = createApi({
reducerPath: 'api',
baseQuery: fetchBaseQuery({ baseUrl: '/api' }),
endpoints: (builder) => ({
getPosts: builder.query({
query: () => 'posts',
}),
}),
});
export const { useGetPostsQuery } = apiSlice;
Redux Toolkit automatically includes the redux-thunk middleware, making it easier to handle asynchronous logic. It also enables Immer by default, allowing state to be mutated safely.
In traditional Redux, you need to manually write a lot of boilerplate, such as action types, action creators, and switch-case reducers. Redux Toolkit significantly reduces this complexity by generating actions and reducers automatically.
createSlice
to simplify reducer logic.createAsyncThunk
for handling async operations.Redux Toolkit simplifies the process of managing global state in React applications. By eliminating boilerplate code and providing useful utilities like createSlice and createAsyncThunk, Redux Toolkit enables you to focus on your application logic rather than the intricacies of state management. Happy coding !❤️