Skip to content

Part 6.5 — Introducing Redux Toolkit the Right Way

Site Console Site Console
3 min read Updated Jan 14, 2026 Frontend Design 0 comments

Redux has a reputation problem—and it earned it.
Most of that pain came from boilerplate, ceremony, and misuse, not from the core idea.

Redux Toolkit (RTK) fixes the how, not the why.
This post explains when Redux is actually justified, and how to adopt Redux Toolkit without recreating old mistakes.


1. When Redux Toolkit Is the Right Tool

You should consider Redux Toolkit when:

  • state spans many routes and features

  • multiple teams touch the same state

  • debugging state transitions matters

  • time-travel debugging is valuable

  • Context + reducers are getting unwieldy

Examples:

  • authenticated user + permissions

  • complex UI workflows (wizards, editors)

  • app-wide notifications

  • cached server data shared across screens

If your app is small or feature-scoped, Context may still be better.


2. What Redux Toolkit Actually Gives You

Redux Toolkit provides:

  • configureStore (sane defaults)

  • createSlice (actions + reducer together)

  • built-in Immer for immutable updates

  • DevTools integration by default

  • great TypeScript inference

It removes 90% of classic Redux boilerplate.


3. Create the Store (Minimal, Explicit)

Install dependencies:

pnpm add @reduxjs/toolkit react-redux

Create store.ts:

import { configureStore } from '@reduxjs/toolkit';

export const store = configureStore({
  reducer: {},
});

export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;

Start empty. Add slices deliberately.


4. Define Your First Slice (Auth Example)

import { createSlice, PayloadAction } from '@reduxjs/toolkit';

type AuthState = {
  user: User | null;
};

const initialState: AuthState = {
  user: null,
};

const authSlice = createSlice({
  name: 'auth',
  initialState,
  reducers: {
    loginSuccess(state, action: PayloadAction<User>) {
      state.user = action.payload;
    },
    logout(state) {
      state.user = null;
    },
  },
});

export const { loginSuccess, logout } = authSlice.actions;
export default authSlice.reducer;

Key points:

  • reducers look “mutable” (Immer handles immutability)

  • actions and reducer live together

  • intent is obvious


5. Register the Slice in the Store

import authReducer from './authSlice';

export const store = configureStore({
  reducer: {
    auth: authReducer,
  },
});

Now your state shape is explicit:

state.auth.user

No magic.


6. Provide the Store to React

import { Provider } from 'react-redux';

<Provider store={store}>
  <App />
</Provider>

This mirrors Context—but with stronger tooling.


7. Typed Hooks (Non-Negotiable)

Create hooks.ts:

import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';
import type { RootState, AppDispatch } from './store';

export const useAppDispatch = () => useDispatch<AppDispatch>();
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;

Never use untyped useDispatch or useSelector.


8. Using Redux in Components

Read state:

const user = useAppSelector(state => state.auth.user);

Dispatch actions:

const dispatch = useAppDispatch();
dispatch(logout());

Components stay declarative and readable.


9. Async Logic with Thunks (Keep It Boring)

Redux Toolkit supports async logic via thunks.

export const login = (email: string, password: string) =>
  async (dispatch: AppDispatch) => {
    const res = await fetch('/api/login', {
      method: 'POST',
      body: JSON.stringify({ email, password }),
    });
    const data = await res.json();
    dispatch(loginSuccess(data.user));
  };

Rules:

  • async logic lives outside reducers

  • reducers remain pure

  • side effects are explicit

RTK also supports createAsyncThunk—use it when async flows grow complex.


10. What Not to Put in Redux

Do not put:

  • form inputs

  • temporary UI state

  • modal open flags

  • component-specific state

Redux is for shared, long-lived, high-value state.


11. Redux vs Context: Clear Boundaries

Concern

Context

Redux

Small app

Feature-scoped

App-wide state

⚠️

Debugging

Large teams

Time travel

Choose based on scale, not habit.


12. Testing Redux Logic

Slices are easy to test:

it('logs user out', () => {
  const state = authReducer(
    { user: { id: 1 } },
    logout(),
  );

  expect(state.user).toBeNull();
});

No React. No DOM. Pure logic.


13. Common Redux Toolkit Mistakes

Avoid:

  • one giant slice

  • deeply nested state

  • putting server state everywhere

  • dispatching from random utilities

  • skipping typed hooks

Redux should feel structured, not sprawling.


14. Summary

You now know:

  • when Redux Toolkit is justified

  • how it differs from Context + reducers

  • how to set it up cleanly

  • how to write slices and actions

  • how to integrate async logic safely

  • what not to store in Redux

In Part 6.6, you’ll close the loop by separating UI state from server state, handling async flows correctly, and integrating REST APIs without mixing concerns.

Related

Leave a comment

Sign in to leave a comment.

Comments