Redux Toolkit: Simplified State Management for React
Redux Toolkit (RTK) is the official, recommended way to write Redux logic. It helps manage global state in React apps efficiently by reducing boilerplate and providing powerful abstractions.
š Why Redux Toolkit?
Traditional Redux requires a lot of setup with action types, action creators, and reducers. RTK simplifies all of this by:
- ā Reducing boilerplate
- š Providing powerful APIs like
createSlice
,configureStore
- š§ Built-in support for Redux Thunk
- š ļø Immutable updates via Immer
š§ Installation
npm install @reduxjs/toolkit react-redux
š§± Setting Up Redux Toolkit
1. Create a Redux Slice
// features/counter/counterSlice.js
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;
2. Configure the Store
// app/store.js
import { configureStore } from "@reduxjs/toolkit";
import counterReducer from "../features/counter/counterSlice";
export const store = configureStore({
reducer: {
counter: counterReducer,
},
});
3. Provide the Store to Your App
// index.js
import React from "react";
import ReactDOM from "react-dom";
import { Provider } from "react-redux";
import { store } from "./app/store";
import App from "./App";
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
);
š¦ Using Redux State in Components
1. Reading State with useSelector
import { useSelector } from "react-redux";
function CounterDisplay() {
const count = useSelector((state) => state.counter.value);
return <h1>Count: {count}</h1>;
}
2. Dispatching Actions with useDispatch
import { useDispatch } from "react-redux";
import { increment, decrement } from "../features/counter/counterSlice";
function CounterButtons() {
const dispatch = useDispatch();
return (
<div>
<button onClick={() => dispatch(increment())}>+</button>
<button onClick={() => dispatch(decrement())}>-</button>
</div>
);
}
āļø Async Logic with Thunks
1. Create an Async Thunk
// features/counter/counterSlice.js
import { createAsyncThunk } from "@reduxjs/toolkit";
export const fetchCount = createAsyncThunk("counter/fetchCount", async () => {
const response = await fetch("/api/count");
const data = await response.json();
return data.value;
});
2. Handle in extraReducers
extraReducers: (builder) => {
builder
.addCase(fetchCount.pending, (state) => {
state.status = "loading";
})
.addCase(fetchCount.fulfilled, (state, action) => {
state.status = "succeeded";
state.value = action.payload;
})
.addCase(fetchCount.rejected, (state) => {
state.status = "failed";
});
};
š§ Best Practices
- Keep slices focused on a single domain (e.g.,
userSlice
,authSlice
) - Use Immer (built-in) for safe state mutation
- Avoid prop drilling by lifting state to Redux when needed
- Use
createAsyncThunk
for side effects - Memoize selectors if needed
š ļø DevTools & Middleware
Redux Toolkit enables Redux DevTools automatically. You can also add middleware:
const store = configureStore({
reducer: rootReducer,
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(customMiddleware),
});
š Folder Structure Suggestion
src/
ā
āāā app/
ā āāā store.js
āāā features/
ā āāā counter/
ā āāā counterSlice.js
ā āāā CounterComponents.js
āāā App.js