Leave a star to support if this helped! ⭐
The Roadmap: Module 05

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.

00

Why Redux Toolkit?

The Problem

Traditional Redux required heavy boilerplate (Action Types, Creators, Reducers) and manual setup for things like DevTools or Thunks.

01

Complex Setup

Too much configuration just to start.

02

Manual Immutability

One tiny mistake in copying state breaks the app.

03

Too Many Files

Jumping between 4 files for one simple feature.

04

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.

01

Store (for data)

The "Big Warehouse" where all your app data lives safely.

02

Slice (feature logic)

A small "box" that keeps one feature's data and logic together.

03

Less Setup / Less Code

Smart shortcuts that do the heavy lifting for you.

04

Better Structure

Clean, organized code that is easy to read and scale.

01

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
02

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

1

Define Slices

Create specialized "data boxes" (e.g., User Slice, Product Slice).

2

Combine in Store

Register all those slices inside the configureStore function.

3

Provide to React

Wrap your App in <Provider> so components can "tune in" to the data.

03

The Data Cycle

How data moves through your application in 3 steps.

Trigger Event

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"))
Process Data

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)
}
Update UI

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.

04

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;
05

Redux in Components

1

useSelector()

Used to read data from the store. React will automatically re-render the component whenever the selected data changes.

// Read state
const name = useSelector(state => state.user.name)
2

useDispatch()

Used to trigger actions. It returns a function that you use to "dispatch" actions to the store.

// Trigger update
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

06

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.

01
Pending

Request started. Time to show a loading spinner.

02
Fulfilled

Success! We received the data from the server.

03
Rejected

Error. Something went wrong with the network.

07

Extra Reducers

The Logic Bridge

The Builder Pattern

extraReducers allow your slice to respond to actions defined outside of itself—most commonly Async Thunks.

Centralized external logic
Chainable builder cases

Pro Tip

Use the builder object. It ensures that your cases are type-safe and prevents accidental state mutations.

userSlice.js
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;
        });
    }
});