Redux Toolkit (RTK) is the official, recommended way to write Redux logic. It simplifies Redux development by reducing boilerplate code and providing powerful utilities for state management.
Why Use Redux Toolkit?
- Less Boilerplate: RTK simplifies writing reducers, actions, and store configuration.
- Immutability with Immer: Allows writing “mutating” code, which is internally converted to immutable updates.
- Built-in Middleware: Includes Redux Thunk for handling asynchronous logic.
- Opinionated Defaults: Encourages best practices like slice-based state organization.
- Built-in DevTools Support: Works seamlessly with Redux DevTools.
How Redux Toolkit Works
The main tools in RTK are:
configureStore
: Simplifies store setup.createSlice
: Combines reducers and actions in one step.createAsyncThunk
: Handles asynchronous logic (like API calls).createSelector
: For memoized state selection.
Setting Up Redux Toolkit in React
1. Install Redux Toolkit
npm install @reduxjs/toolkit react-redux
2. Create a Slice
A slice represents a piece of state and includes its actions and reducers.
3. Configure the Store
Use configureStore
to set up the Redux store.
import { configureStore } from '@reduxjs/toolkit'; import counterReducer from './counterSlice'; const store = configureStore({ reducer: { counter: counterReducer, }, }); export default store;
4. Provide the Store to React
Wrap your application in the Redux Provider
to make the store accessible to all components.
import React from 'react'; import ReactDOM from 'react-dom'; import { Provider } from 'react-redux'; import store from './store'; import App from './App'; ReactDOM.render( <Provider store={store}> <App /> </Provider>, document.getElementById('root') );
5. Access State and Dispatch Actions
Use useSelector
to access state and useDispatch
to dispatch actions in your components.
import React from 'react'; import { useSelector, useDispatch } from 'react-redux'; import { increment, decrement, reset } from './counterSlice'; const Counter = () => { const count = useSelector((state) => state.counter.count); // Access state const dispatch = useDispatch(); // Dispatch actions return ( <div> <h1>Count: {count}</h1> <button onClick={() => dispatch(increment())}>Increment</button> <button onClick={() => dispatch(decrement())}>Decrement</button> <button onClick={() => dispatch(reset())}>Reset</button> </div> ); }; export default Counter;
Handling Async Logic with createAsyncThunk
createAsyncThunk
simplifies working with async logic like API calls.
1. Define an Async Thunk
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'; // Async thunk for fetching data export const fetchUser = createAsyncThunk('user/fetchUser', async (userId) => { const response = await fetch(`https://jsonplaceholder.typicode.com/users/${userId}`); return response.json(); }); // Create slice const userSlice = createSlice({ name: 'user', initialState: { data: null, status: 'idle', // 'idle' | 'loading' | 'succeeded' | 'failed' error: null, }, reducers: {}, extraReducers: (builder) => { builder .addCase(fetchUser.pending, (state) => { state.status = 'loading'; }) .addCase(fetchUser.fulfilled, (state, action) => { state.status = 'succeeded'; state.data = action.payload; }) .addCase(fetchUser.rejected, (state, action) => { state.status = 'failed'; state.error = action.error.message; }); }, }); export default userSlice.reducer;
2. Use the Thunk in a Component
import React, { useEffect } from 'react'; import { useSelector, useDispatch } from 'react-redux'; import { fetchUser } from './userSlice'; const User = () => { const dispatch = useDispatch(); const user = useSelector((state) => state.user.data); const status = useSelector((state) => state.user.status); useEffect(() => { dispatch(fetchUser(1)); // Fetch user with ID 1 }, [dispatch]); if (status === 'loading') return <p>Loading...</p>; if (status === 'failed') return <p>Error fetching user data.</p>; return ( <div> {user && ( <> <h1>{user.name}</h1> <p>{user.email}</p> </> )} </div> ); }; export default User;
Debugging with Redux DevTools
configureStore
automatically enables Redux DevTools, making it easy to monitor state changes and dispatched actions.
Best Practices with Redux Toolkit
- Use Slices for Modular State: Create separate slices for each feature or domain.
- Avoid Nesting State: Keep your state as flat as possible for better performance.
- Use Middleware for Side Effects: Leverage
createAsyncThunk
or custom middleware for async logic. - Memoize Selectors: Use
createSelector
fromreselect
to avoid unnecessary re-renders. - Use DevTools: Monitor dispatched actions and state changes during development.