Redux Toolkit & Slices in React – Simplified State Management (2025 Guide)
Redux is powerful, but it involves boilerplate code: defining actions, action types, reducers, and dispatching manually.
Redux Toolkit (RTK) is the official, recommended approach for writing Redux logic in modern React apps.
Redux Toolkit simplifies Redux by reducing boilerplate and providing a structured way to manage state.
Key Benefits:
Less boilerplate (no separate action creators or switch statements)
Built-in support for immutable state updates using
ImmerSimplifies async logic with
createAsyncThunk
What is a Slice?
A slice is a single unit of Redux logic for a particular piece of state. It contains:
Initial state
Reducers (functions to update state)
Actions (automatically generated)
RTK combines actions and reducers into one place.
Example: Counter Slice
// counterSlice.js
import { createSlice } from "@reduxjs/toolkit";
export const counterSlice = createSlice({
name: "counter",
initialState: { count: 0 },
reducers: {
increment: (state) => {
state.count += 1; // Immer allows direct state mutation
},
decrement: (state) => {
state.count -= 1;
},
reset: (state) => {
state.count = 0;
},
incrementByAmount: (state, action) => {
state.count += action.payload;
},
},
});
// Export actions automatically generated
export const { increment, decrement, reset, incrementByAmount } = counterSlice.actions;
// Export reducer for store
export default counterSlice.reducer;
Notice how actions and reducers are combined — no separate files needed.
Configuring Store with Redux Toolkit
// store.js
import { configureStore } from "@reduxjs/toolkit";
import counterReducer from "./counterSlice";
export const store = configureStore({
reducer: {
counter: counterReducer,
},
});
configureStoreautomatically sets up:Redux DevTools
Middleware like
redux-thunk
Makes store configuration simpler and safer
Using Redux Toolkit in React
import React from "react";
import ReactDOM from "react-dom";
import { Provider, useDispatch, useSelector } from "react-redux";
import { store } from "./store";
import { increment, decrement, reset, incrementByAmount } from "./counterSlice";
function Counter() {
const count = useSelector((state) => state.counter.count);
const dispatch = useDispatch();
return (
<div style={{ textAlign: "center" }}>
<h2>Count: {count}</h2>
<button onClick={() => dispatch(increment())}>+</button>
<button onClick={() => dispatch(decrement())}>-</button>
<button onClick={() => dispatch(reset())}>Reset</button>
<button onClick={() => dispatch(incrementByAmount(5))}>+5</button>
</div>
);
}
ReactDOM.render(
<Provider store={store}>
<Counter />
</Provider>,
document.getElementById("root")
);
Using Redux Toolkit, updating state is clean and readable.
Async Logic with createAsyncThunk
For API calls, Redux Toolkit provides createAsyncThunk:
import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
export const fetchUsers = createAsyncThunk(
"users/fetchUsers",
async () => {
const response = await fetch("https://jsonplaceholder.typicode.com/users");
return response.json();
}
);
const usersSlice = createSlice({
name: "users",
initialState: { list: [], status: "idle" },
reducers: {},
extraReducers: (builder) => {
builder
.addCase(fetchUsers.pending, (state) => {
state.status = "loading";
})
.addCase(fetchUsers.fulfilled, (state, action) => {
state.status = "succeeded";
state.list = action.payload;
})
.addCase(fetchUsers.rejected, (state) => {
state.status = "failed";
});
},
});
export default usersSlice.reducer;
Automatically handles pending, fulfilled, and rejected states
Makes async state management clean and predictable
Key Advantages of Redux Toolkit
| Feature | Benefit |
|---|---|
createSlice | Combines actions and reducers, reduces boilerplate |
configureStore | Auto configures DevTools and middleware |
Immer | Allows safe direct state mutation |
createAsyncThunk | Simplifies async API calls |