Redux Toolkit:
The Modern State.
Think of Redux as a single "brain" for your app. Instead of passing data between every page manually, Redux Toolkit lets you store everything in one easy-to-reach place that any component can talk to instantly.
Forget messy setups and confusing code. In the first version of Redux, things were complicated and slow to build. Now, you can master a clean, simple way to move data through your app with tools that are predictable and built for the modern web.
Why Redux Toolkit?
The Problem
Traditional Redux required heavy boilerplate (Action Types, Creators, Reducers) and manual setup for things like DevTools or Thunks.
Complex Setup
Too much configuration just to start.
Manual Immutability
One tiny mistake in copying state breaks the app.
Too Many Files
Jumping between 4 files for one simple feature.
Store Verbosity
Writing huge code blocks for small data changes.
The Solution: RTK
Redux Toolkit is the official, opinionated toolset. It handles immutability, reduces code by 40%, and comes pre-configured for speed.
Store (for data)
The "Big Warehouse" where all your app data lives safely.
Slice (feature logic)
A small "box" that keeps one feature's data and logic together.
Less Setup / Less Code
Smart shortcuts that do the heavy lifting for you.
Better Structure
Clean, organized code that is easy to read and scale.
Get the SDK
@reduxjs/toolkit
Includes everything needed to manage state: reducers, actions, and thunks. It's the logic engine.
react-redux
The bridge that connects your React components to the Redux store. It lets components "talk" to data.
npm i @reduxjs/toolkit react-redux
Understanding the Store
// src/app/store.js import { configureStore } from '@reduxjs/toolkit' import counterReducer from '../features/counter/slice' export const store = configureStore({ reducer: { counter: counterReducer, }, })
What is a Store?
The "Single Source of Truth." Think of it as a central database sitting inside your browser. Instead of passing data manually through 10 components, every component just asks the Store.
Key Fact
"One App = One Store. We don't create multiple stores; we create multiple Slices."
The Setup Flow
Define Slices
Create specialized "data boxes" (e.g., User Slice, Product Slice).
Combine in Store
Register all those slices inside the configureStore function.
Provide to React
Wrap your App in <Provider>
so components can "tune in" to the data.
The Data Cycle
How data moves through your application in 3 steps.
1. Dispatching
Use Case: Used whenever a user does something—clicks a button, submits a form, or deletes an item.
It's like telling a waiter: "Take this request to the kitchen."
In your Component:
dispatch(addItem("Pizza"))
2. Reducer
Use Case: Used to define how the state changes. It calculates the new data based on the action received.
The Reducer is the Chef. It's the only one allowed to modify the store.
In your Slice:
addItem: (state, action) => {
state.items.push(action.payload)
}
3. Selector
Use Case: Used to pull specific data into a component and keep it in sync with the global store.
As soon as the Chef adds the Pizza, the UI automatically rerenders to show it.
In your Component:
const items = useSelector(
state => state.cart.items
)
It's okay if this feels heavy right now!
Theoretical Redux can be confusing to read. If you're struggling to visualize the flow, keep going. Everything will "click" instantly once we start writing the actual code in the real project.
Slices & Immer
What is a Slice?
A slice is a bundle of Redux logic for a single feature (like "auth" or "cart"). It contains the initial state, reducers, and actions in one place, making your code modular and easy to manage.
Mutable Syntax (Immer)
Inside createSlice, you can
write state.value += 1. RTK uses the
Immer library to ensure this is converted into a safe, immutable update automatically.
No more spread operators ...state
everywhere!
const userSlice = createSlice({ name: 'user', initialState: { name: '' }, reducers: { setName: (state, action) => { // Direct mutation is allowed here! state.name = action.payload; } } }) // Exporting for use in components export const { setName } = userSlice.actions; export default userSlice.reducer;
Redux in Components
useSelector()
Used to read data from the store. React will automatically re-render the component whenever the selected data changes.
const name = useSelector(state => state.user.name)
useDispatch()
Used to trigger actions. It returns a function that you use to "dispatch" actions to the store.
dispatch(setName('Gemini'))
Cheat Sheet: When to use what?
Goal
I need to show the user's name on a profile page.
Use useSelector
Goal
I need to update the shopping cart when a button is clicked.
Use useDispatch
Goal
I need to reset everything when the user logs out.
Use useDispatch
Async Logic
Handling Side Effects with createAsyncThunk
Reducers must be pure. To handle API calls, we use Thunks. A Thunk automatically generates three action types representing the lifecycle of the request.
Request started. Time to show a loading spinner.
Success! We received the data from the server.
Error. Something went wrong with the network.
Extra Reducers
The Logic Bridge
The Builder Pattern
extraReducers allow your slice to respond to actions defined outside of itself—most commonly Async Thunks.
Pro Tip
Use the builder object. It ensures that your cases are type-safe and prevents accidental state mutations.
const userSlice = createSlice({ name: 'user', initialState, extraReducers: (builder) => { builder // Handle Pending state .addCase(fetchData.pending, (state) => { state.loading = true; }) // Handle Success state .addCase(fetchData.fulfilled, (state, action) => { state.loading = false; state.data = action.payload; }); } });