Redux Toolkit: Modern Redux Made Easy

Managing state across large React applications can be complex. Traditionally, Redux has been the go-to solution for state management in React, but its boilerplate-heavy setup and learning curve often made it cumbersome. Enter Redux Toolkit, a modern approach to Redux that simplifies setup, reduces boilerplate, and improves developer experience.

In this module, we will explore Redux Toolkit, learn how to set up a Redux store, and how to integrate it into your React applications with fewer complexities compared to traditional Redux.


Table of Contents

  1. What is Redux Toolkit?
  2. Why Use Redux Toolkit?
  3. Setting Up Redux Toolkit
  4. Creating a Slice with createSlice
  5. Configuring the Redux Store
  6. Dispatching Actions and Reading State
  7. Using Redux Toolkit with React Components
  8. Advanced Features: createAsyncThunk
  9. Optimizing Redux State with createEntityAdapter
  10. Code Example: A Counter App with Redux Toolkit
  11. Best Practices for Using Redux Toolkit
  12. Conclusion

1. What is Redux Toolkit?

Redux Toolkit is an official package that provides a set of utilities to simplify Redux development. It was created to address the common pain points developers faced when using traditional Redux, such as boilerplate code and difficult configuration. Redux Toolkit includes several APIs that streamline the Redux process and encourage best practices.

Some of its key features include:

  • createSlice: Automatically generates action creators and reducers.
  • configureStore: Simplifies store setup and includes built-in devtools and middleware.
  • createAsyncThunk: For handling asynchronous logic in a more readable way.
  • createEntityAdapter: Helps manage normalized data in the Redux state.

2. Why Use Redux Toolkit?

Redux Toolkit makes working with Redux easier and more intuitive. Here are some reasons to use it:

  • Reduces boilerplate: You no longer have to manually write action types, action creators, or reducers.
  • Improved performance: The toolkit includes optimizations for performance, like caching and memoization.
  • Encourages best practices: With configureStore and createSlice, Redux Toolkit encourages cleaner and more maintainable code.
  • Simplified async handling: With createAsyncThunk, Redux Toolkit makes it much easier to deal with asynchronous actions.

By using Redux Toolkit, you can focus more on writing the logic of your application instead of setting up and managing Redux.


3. Setting Up Redux Toolkit

Before you start using Redux Toolkit in your application, you need to install it. To do so, use the following command:

npm install @reduxjs/toolkit react-redux

Once installed, you can begin setting up Redux Toolkit in your app.


4. Creating a Slice with createSlice

In Redux Toolkit, the concept of a slice represents a piece of the Redux state along with its corresponding actions and reducers. The createSlice function simplifies the creation of slices by automatically generating the necessary actions and reducers.

Here’s how you can create a slice:

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

const initialState = {
count: 0,
};

const counterSlice = createSlice({
name: 'counter',
initialState,
reducers: {
increment: (state) => {
state.count += 1;
},
decrement: (state) => {
state.count -= 1;
},
},
});

export const { increment, decrement } = counterSlice.actions;
export default counterSlice.reducer;
  • name: The name of the slice (used for debugging).
  • initialState: The initial state of the slice.
  • reducers: An object where each key is an action and the value is the corresponding reducer function.

In this example, the slice manages a simple counter with increment and decrement actions.


5. Configuring the Redux Store

After creating slices, you need to configure the Redux store. The configureStore function simplifies store creation and sets up Redux DevTools extension and default middleware.

Here’s how you configure the store:

import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './counterSlice';

const store = configureStore({
reducer: {
counter: counterReducer,
},
});

export default store;

Here, we combine our slice reducer (counterReducer) into the store’s root reducer.


6. Dispatching Actions and Reading State

Once your store is set up, you can use the dispatch function to dispatch actions and useSelector to access state from any React component.

Dispatching Actions

To dispatch an action, you can use the useDispatch hook:

import { useDispatch } from 'react-redux';
import { increment, decrement } from './counterSlice';

function Counter() {
const dispatch = useDispatch();

return (
<div>
<button onClick={() => dispatch(increment())}>Increment</button>
<button onClick={() => dispatch(decrement())}>Decrement</button>
</div>
);
}

Reading State

To read the state, you can use the useSelector hook:

import { useSelector } from 'react-redux';

function Counter() {
const count = useSelector((state) => state.counter.count);
return <div>Current Count: {count}</div>;
}

In this example, useSelector reads the count value from the store and displays it in the component.


7. Using Redux Toolkit with React Components

Once you’ve set up your store and slices, you can easily integrate Redux Toolkit into your React components. Simply use useDispatch to dispatch actions and useSelector to read the state.

This makes it simple to manage state in your components without needing to manually pass props down through the component tree or rely on complex state management solutions.


8. Advanced Features: createAsyncThunk

Handling asynchronous logic like API calls is one of the more challenging aspects of Redux. Redux Toolkit introduces createAsyncThunk to simplify this process by automatically generating action creators and reducers for async logic.

Here’s an example of how to use createAsyncThunk to fetch data:

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

export const fetchData = createAsyncThunk(
'data/fetchData',
async () => {
const response = await fetch('https://api.example.com/data');
return response.json();
}
);

const dataSlice = createSlice({
name: 'data',
initialState: {
items: [],
status: 'idle',
},
reducers: {},
extraReducers: (builder) => {
builder
.addCase(fetchData.pending, (state) => {
state.status = 'loading';
})
.addCase(fetchData.fulfilled, (state, action) => {
state.status = 'succeeded';
state.items = action.payload;
})
.addCase(fetchData.rejected, (state) => {
state.status = 'failed';
});
},
});

export default dataSlice.reducer;

In this example:

  • createAsyncThunk handles async logic.
  • extraReducers handles the different states of the async action (pending, fulfilled, rejected).

9. Optimizing Redux State with createEntityAdapter

Managing normalized data is often a challenge in Redux. createEntityAdapter helps by automatically handling normalized collections (arrays of objects) and providing useful methods like addOne, updateOne, and removeOne.

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

const usersAdapter = createEntityAdapter();

const initialState = usersAdapter.getInitialState();

const usersSlice = createSlice({
name: 'users',
initialState,
reducers: {
addUser: usersAdapter.addOne,
removeUser: usersAdapter.removeOne,
},
});

export default usersSlice.reducer;

Using createEntityAdapter ensures efficient updates to collections, improving performance and simplifying your Redux store logic.


10. Code Example: A Counter App with Redux Toolkit

Let’s put everything together in a simple counter app using Redux Toolkit:

import React from 'react';
import { Provider, useDispatch, useSelector } from 'react-redux';
import store, { increment, decrement } from './counterSlice';

function Counter() {
const count = useSelector((state) => state.counter.count);
const dispatch = useDispatch();

return (
<div>
<h1>Count: {count}</h1>
<button onClick={() => dispatch(increment())}>Increment</button>
<button onClick={() => dispatch(decrement())}>Decrement</button>
</div>
);
}

function App() {
return (
<Provider store={store}>
<Counter />
</Provider>
);
}

export default App;

11. Best Practices for Using Redux Toolkit

  • Use slices: Break up the state into smaller slices that handle specific parts of the state.
  • Use createAsyncThunk for async logic: It simplifies the handling of async actions.
  • Leverage createEntityAdapter for normalized data: Use it for efficiently managing lists of entities.
  • Avoid mutating the state: Always use the Immer library (which is included in Redux Toolkit) to work with immutable state directly.
  • Use selectors: Instead of accessing state directly, create reusable selectors for reading from the state.

12. Conclusion

Redux Toolkit is a modern and simplified approach to Redux that eliminates much of the boilerplate code traditionally associated with state management in React. By using slices, createAsyncThunk, and createEntityAdapter, you can manage both simple and complex state efficiently.