Redux Toolkit is the official, recommended way to use Redux with React. It bundles store setup, reducer creation, and immutable update logic so you write far less code than classic Redux. This tutorial builds a working counter from one store and one slice.
Install the packages
Redux Toolkit and React Redux are two packages that work together. Install both, since the store setup comes from the first and the React bindings come from the second.
npm install @reduxjs/toolkit react-reduxRedux Toolkit provides configureStore and createSlice. React Redux provides Provider and the useSelector and useDispatch hooks.
Create the store
A store is one object that holds the whole app state. configureStore creates it and turns on the Redux DevTools browser extension automatically.
import { configureStore } from "@reduxjs/toolkit";
import counterReducer from "./features/counter/counterSlice";
export const store = configureStore({
reducer: {
counter: counterReducer,
},
});The reducer object maps each state key to a slice reducer. Here the counter key holds whatever the counter slice returns.
The shape of that object becomes the shape of the entire state tree. configureStore also adds thunk middleware by default, so async action creators work without extra setup.
Provide the store
Wrap the app in Provider so every component can read the store. Pass the store as a prop.
import { createRoot } from "react-dom/client";
import { Provider } from "react-redux";
import { store } from "./app/store";
import App from "./App";
createRoot(document.getElementById("root")).render(
<Provider store={store}>
<App />
</Provider>
);Provider exposes the store to the whole tree through context. The hooks read that context under the hood, so you never pass the store manually. One Provider at the root is enough, because context flows down to every component automatically.
Build a slice
A slice groups one piece of state with the reducers that update it. createSlice generates action creators and a reducer for you.
import { createSlice } from "@reduxjs/toolkit";
export const counterSlice = createSlice({
name: "counter",
initialState: { value: 0 },
reducers: {
increment(state) { state.value += 1; },
decrement(state) { state.value -= 1; },
},
});
export const { increment, decrement } = counterSlice.actions;
export default counterSlice.reducer;The reducers look like they mutate state, but createSlice runs them through Immer. Immer records the changes on a draft and returns a brand new immutable state object, which keeps updates predictable. Slices can also read a payload, but this simple form shows the whole pattern.
Read and update from a component
Components use useSelector to read state and useDispatch to send actions.
import { useSelector, useDispatch } from "react-redux";
import { increment } from "./counterSlice";
export function Counter() {
const count = useSelector((state) => state.counter.value);
const dispatch = useDispatch();
return (
<>
<button onClick={() => dispatch(increment())}>+</button>
<span>{count}</span>
</>
);
}Clicking the plus button dispatches the increment action, the slice updates state, and the component re-renders with the new count. The decrement action works the same way. useSelector compares the value it returns, so the component re-renders only when its slice of state actually changed.
How a dispatch flows
One update travels in a fixed direction. The component dispatches an action, the matching reducer returns the next state, the store saves it, and React re-renders the components that selected a changed value. Nothing changes until an action is dispatched, so the flow is easy to trace when a value looks wrong.
This loop is the core of Redux, and Redux Toolkit keeps the loop but removes the wiring. You never call a reducer directly or write an action type constant, so there is less code to keep in sync as the app grows. The same slice can be reused anywhere in the tree without passing callbacks down.
When Redux Toolkit is the right fit
This structure shines in larger apps where a predictable single store, action history, and middleware matter. For a small app, the same counter is easier with useState, and the tradeoffs are covered in the state management decision guide. If the boilerplate feels heavy, compare it with Zustand.
It also manages client state, not server cache. Fetching, caching, and revalidating live better in a data library such as TanStack Query, as explained in client state vs server state in React.
Common mistakes
- Importing the store into components instead of using the hooks.
- Mutating state outside the slice reducers.
- Storing server cache alongside client state.
Each mistake reintroduces a problem the library was designed to remove. Keep dispatch in components, mutations inside reducers, and server data in a data library. When in doubt, trace the data flow back from the component to the slice.
Rune AI
Key Insights
- Install @reduxjs/toolkit and react-redux together.
- Create the store with configureStore and a reducer map.
- Wrap the app in Provider from react-redux.
- Build slices with createSlice and read them with useSelector and useDispatch.
Frequently Asked Questions
What is the difference between Redux and Redux Toolkit?
Do I need react-redux?
Can I use Redux Toolkit without React?
Conclusion
Redux Toolkit turns Redux into a small, structured setup. configureStore builds the store, createSlice groups state with its reducers, and react-redux hooks read and update state from components.
More in this topic
How to Build a Dropdown Menu in React
Build a React dropdown menu with the ARIA menu button pattern. Handle open and close, keyboard arrows, and clicks outside the menu.
How to Animate React Components with Motion
Animate React components with the Motion library. Set up motion, add enter, hover, and exit animations, and respect reduced motion.
Headless UI Components Explained: Logic Without Locked Styling
Understand headless UI components and how libraries like Radix give you unstyled, accessible behavior that you style yourself.