Redux Toolkit
Redux Toolkit (RTK) is the official, opinionated, batteries-included toolset for efficient Redux development. It is intended to be the standard way to write Redux logic and addresses the common complaints about Redux being too verbose, requiring too much boilerplate, and being difficult to configure.
Installation
npm install @reduxjs/toolkit react-redux
Core Concepts
configureStore
configureStore wraps createStore to provide simplified configuration options and good defaults. It automatically combines your slice reducers, adds whatever Redux middleware you supply, includes redux-thunk by default, and enables use of the Redux DevTools Extension.
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from '../features/counter/counterSlice';
export const store = configureStore({
reducer: {
counter: counterReducer,
},
});
createSlice
createSlice is a function that accepts an initial state, an object of reducer functions, and a "slice name", and automatically generates action creators and action types that correspond to the reducers and state.
This drastically reduces boilerplate. Additionally, Redux Toolkit uses the Immer library under the hood, which allows you to write "mutating" logic in reducers instead of returning new state objects manually.
import { createSlice } from '@reduxjs/toolkit';
const initialState = {
value: 0,
};
export const counterSlice = createSlice({
name: 'counter',
initialState,
reducers: {
increment: (state) => {
// Redux Toolkit allows us to write "mutating" logic in reducers. It
// doesn't actually mutate the state because it uses the Immer library.
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 Redux in React Components
To interact with the Redux store from React components, you use the hooks provided by react-redux.
useSelector
The useSelector hook allows you to extract data from the Redux store state. The selector function is called with the entire Redux store state as its only argument.
useDispatch
The useDispatch hook returns a reference to the dispatch function from the Redux store. You use it to dispatch actions as needed.
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { increment, decrement, incrementByAmount } from './counterSlice';
export 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>
<button onClick={() => dispatch(incrementByAmount(5))}>
Add 5
</button>
</div>
</div>
);
}
Asynchronous Logic: createAsyncThunk
Redux Toolkit includes createAsyncThunk for handling asynchronous actions, typically API calls. It generates action creators that dispatch pending, fulfilled, and rejected actions based on a promise-returning function you provide.
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';
export const fetchUserById = createAsyncThunk(
'users/fetchByIdStatus',
async (userId, thunkAPI) => {
const response = await fetch(`https://api.example.com/users/${userId}`);
return await response.json();
}
);
const usersSlice = createSlice({
name: 'users',
initialState: { entities: [], loading: 'idle' },
reducers: {},
extraReducers: (builder) => {
builder
.addCase(fetchUserById.pending, (state) => {
state.loading = 'loading';
})
.addCase(fetchUserById.fulfilled, (state, action) => {
state.loading = 'idle';
state.entities.push(action.payload);
});
},
});
Redux Toolkit fundamentally simplifies how you write global state management logic in React applications, making Redux approachable and scalable.