Skip to main content

Redux Toolkit

What is Redux Toolkit?

Redux Toolkit is the officially recommended way to write Redux logic. It simplifies common Redux tasks and helps prevent common mistakes. It includes utilities to simplify several common Redux use cases, including store setup, defining reducers, immutable update logic, and more.

Key Features

  • configureStore(): Wraps createStore to provide simplified setup options and good defaults. Can automatically add Redux DevTools extension, include middleware, and more.
  • createSlice(): Accepts a reducer function, a slice name, and an initial state value, and automatically generates action creators and action types that correspond to the reducers and state.
  • createAsyncThunk(): Simplifies dealing with asynchronous logic like fetching data. It automatically generates pending, fulfilled, and rejected action types.
  • createReducer(): Allows writing "mutating" logic in reducers, thanks to Immer.
  • createAction(): Generates an action creator function for the given action type.

Installation

npm install @reduxjs/toolkit
# or
yarn add @reduxjs/toolkit

Usage Examples

Basic Store Setup

import { configureStore } from "@reduxjs/toolkit";

const store = configureStore({
reducer: {
// Add reducers here when you add your slices
},
});

export default store;

Creating a Slice

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

const counterSlice = createSlice({
name: "counter",
initialState: {
value: 0,
},
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;

Using the Slice in a Component

import React from "react";
import { useSelector, useDispatch } from "react-redux";
import { increment, decrement, incrementByAmount } from "./counterSlice";

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

return (
<div>
<div>
<button
aria-label="Increment value"
onClick={() => dispatch(increment())}
>
Increment
</button>
<span>{count}</span>
<button
aria-label="Decrement value"
onClick={() => dispatch(decrement())}
>
Decrement
</button>
</div>
<div>
<button onClick={() => dispatch(incrementByAmount(5))}>Add 5</button>
</div>
</div>
);
}

export default Counter;

Creating an Async Thunk

import { createAsyncThunk } from "@reduxjs/toolkit";

export const fetchUserById = createAsyncThunk(
"users/fetchByIdStatus",
async (userId, thunkAPI) => {
const response = await fetch(`https://api.example.com/users/${userId}`);
const data = await response.json();
return data;
}
);

Then, add the extraReducers field to your slice to listen for the thunk's actions:

const usersSlice = createSlice({
name: "users",
initialState: {
entities: [],
loading: "idle",
error: null,
},
reducers: {
// omit existing reducers here
},
extraReducers: (builder) => {
builder.addCase(fetchUserById.pending, (state) => {
state.loading = "loading";
});
builder.addCase(fetchUserById.fulfilled, (state, action) => {
state.loading = "idle";
state.entities.push(action.payload);
});
builder.addCase(fetchUserById.rejected, (state, action) => {
state.loading = "idle";
state.error = action.error.message;
});
},
});

Benefits of using Redux Toolkit

  • Simplified Redux development: Reduces boilerplate code.
  • Improved readability: Easier to understand and maintain.
  • Better performance: Immer integration allows for efficient state updates.
  • Less error-prone: Opinionated defaults and standardized approach.
  • Official recommendation: Ensures best practices and long-term support.

Further Resources