0% completed
Redux Toolkit (often abbreviated as RTK) is the official, recommended way to write Redux logic. It is a set of tools and utilities that simplify the process of working with Redux and provide best practices for handling state management.
Redux Toolkit reduces the boilerplate code required for Redux operations and provides an optimized development experience for Redux users.
Before Redux Toolkit, setting up Redux could require a lot of manual configuration, including writing action types, action creators, reducers, and managing middleware. Redux Toolkit abstracts away much of this complexity, making Redux easier to use and reducing errors.
Redux Toolkit was created to address several common pain points in the Redux ecosystem:
Boilerplate Code: Writing Redux actions and reducers can lead to repetitive, verbose code. Redux Toolkit eliminates the need for manually defining action types, creating action creators, and managing switch cases in reducers.
Immutability: Redux requires immutability, meaning that the state must not be mutated directly. While Redux provides ways to handle this, it can sometimes be cumbersome and error-prone.
Configuration Overhead: Setting up a Redux store, middleware, and other tools in vanilla Redux can be error-prone and requires a lot of boilerplate. Redux Toolkit simplifies this setup with sensible defaults and pre-configured utilities.
Good Practices: It promotes best practices like using Redux DevTools, simplifying async logic with createAsyncThunk, and leveraging the redux-logger middleware for debugging.
Redux Toolkit offers several utilities and enhancements to make Redux development faster and less error-prone:
configureStore()
: This function simplifies the store setup, automatically including Redux DevTools, thunk middleware, and allows you to customize the store as needed.createSlice()
: A utility function to simplify creating action creators and reducers in one step.createAsyncThunk()
: A utility to handle asynchronous actions (such as API calls) more easily, with built-in support for dispatching loading, success, and failure actions.createEntityAdapter()
: A utility to simplify working with collections of items in Redux, such as managing lists of items with CRUD operations.Let’s dive into each of these features in more detail with examples.
The configureStore()
function is the primary function in Redux Toolkit for setting up the Redux store. It automatically includes Redux DevTools, Redux Thunk, and other necessary middleware for asynchronous actions, so you don't need to manually configure them.
Let's consider an example of creating a Store with configureStore()
:
// store.js import { configureStore } from '@reduxjs/toolkit'; import todosReducer from './todosSlice'; const store = configureStore({ reducer: { todos: todosReducer, // Your slice reducers go here }, }); export default store;
In this example,
reducer
property where each key corresponds to a piece of the state, and the value is the reducer (usually created via createSlice()
).configureStore()
enables Redux DevTools, so you can inspect actions and state changes in your browser.createSlice()
is one of the most important features of Redux Toolkit. It is a function that automatically generates action creators and reducers for you based on the slice of state you are managing. This greatly reduces the amount of boilerplate code.
Let's consider an example of creating a Slice for To-Dos:
// todosSlice.js import { createSlice } from '@reduxjs/toolkit'; const todosSlice = createSlice({ name: 'todos', // The slice name initialState: [], // Initial state for this slice reducers: { addTodo: (state, action) => { state.push({ id: Date.now(), text: action.payload.text, completed: false, }); }, toggleTodo: (state, action) => { const todo = state.find(todo => todo.id === action.payload.id); if (todo) { todo.completed = !todo.completed; } }, removeTodo: (state, action) => { return state.filter(todo => todo.id !== action.payload.id); }, }, }); // Export the actions and reducer from the slice export const { addTodo, toggleTodo, removeTodo } = todosSlice.actions; export default todosSlice.reducer;
Explanation:
addTodo
, toggleTodo
, and removeTodo
) and a reducer that handles those actions.reducers
object, each function is responsible for modifying the state in response to specific actions. Thanks to Immer, you can directly mutate the state inside the reducers, and Immer will take care of producing an immutable state.addTodo
, toggleTodo
, removeTodo
), which you can dispatch in your components.Redux Toolkit provides a utility function called createAsyncThunk()
to simplify handling asynchronous actions, such as API calls. It automatically dispatches actions for pending, fulfilled, and rejected states, which makes it easier to track loading, success, and error states.
Let's look at an example of fetching To-Dos with createAsyncThunk()
// todosSlice.js import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'; import axios from 'axios'; // Define an async thunk to fetch to-dos from an API export const fetchTodos = createAsyncThunk('todos/fetchTodos', async () => { const response = await axios.get('https://api.example.com/todos'); return response.data; }); const todosSlice = createSlice({ name: 'todos', initialState: { items: [], status: 'idle', // 'idle' | 'loading' | 'succeeded' | 'failed' error: null, }, reducers: { addTodo: (state, action) => { state.items.push({ id: Date.now(), text: action.payload.text, completed: false, }); }, toggleTodo: (state, action) => { const todo = state.items.find(todo => todo.id === action.payload.id); if (todo) { todo.completed = !todo.completed; } }, removeTodo: (state, action) => { state.items = state.items.filter(todo => todo.id !== action.payload.id); }, }, extraReducers: (builder) => { builder .addCase(fetchTodos.pending, (state) => { state.status = 'loading'; // Mark loading state }) .addCase(fetchTodos.fulfilled, (state, action) => { state.status = 'succeeded'; // Mark successful state state.items = action.payload; // Store the fetched todos }) .addCase(fetchTodos.rejected, (state, action) => { state.status = 'failed'; // Mark failed state state.error = action.error.message; // Store the error message }); }, }); export const { addTodo, toggleTodo, removeTodo } = todosSlice.actions; export default todosSlice.reducer;
Explanation:
createAsyncThunk(): The fetchTodos
thunk sends an HTTP GET request to fetch to-dos. It automatically dispatches three actions:
pending
: When the request is being made.fulfilled
: When the request succeeds, and the data is available.rejected
: When the request fails.extraReducers: You can handle the lifecycle of async actions (pending, fulfilled, and rejected) using extraReducers
. This ensures that Redux handles actions that are not part of the slice's reducer but belong to async operations like API requests.
Redux Toolkit also provides a utility called createEntityAdapter()
that simplifies managing collections of items in your state, such as lists of to-dos. It comes with pre-built methods to handle common tasks like adding, updating, and removing items in an efficient and normalized way.
Here's an example using createEntityAdapter()
to manage Todos
// todosSlice.js import { createSlice, createEntityAdapter } from '@reduxjs/toolkit'; const todosAdapter = createEntityAdapter({ selectId: (todo) => todo.id, // Tell the adapter how to identify each item }); const initialState = todosAdapter.getInitialState({ status: 'idle', error: null, }); const todosSlice = createSlice({ name: 'todos', initialState, reducers: { addTodo: todosAdapter.addOne, updateTodo: todosAdapter.updateOne, removeTodo: todosAdapter.removeOne, }, extraReducers: (builder) => { // Add async actions handling as needed }, }); export const { addTodo, updateTodo, removeTodo } = todosSlice.actions; export default todosSlice.reducer;
Explanation:
addOne
, removeOne
, updateOne
: These are built-in methods provided by the entity adapter to manage state in an optimized manner.There are several advantages of using RTK such as:
createEntityAdapter()
, allow for better management of large collections of items in the Redux store.In conclusion, Redux Toolkit is the modern and official way to manage state with Redux in React applications. It simplifies the process of configuring Redux, reduces boilerplate code, and encourages best practices for handling state management. With utilities like createSlice()
, createAsyncThunk()
, and createEntityAdapter()
, Redux Toolkit makes Redux development faster, more efficient, and less error-prone.
Whether you're starting a new project or refactoring an existing Redux setup, using Redux Toolkit is highly recommended as it dramatically improves the developer experience and helps you avoid common pitfalls in Redux development.
.....
.....
.....