Redux Toolkit

Master Redux Toolkit — the modern, opinionated way to write Redux. Learn slices, thunks, RTK Query, and when Redux is actually the right choice for enterprise React applications.

Last reviewed: January 2025

·React 16+React 18Redux Toolkit 2.x
✅
Quick Understanding: Redux Toolkit (RTK) is the official, recommended way to write Redux. It eliminates the boilerplate of classic Redux with createSlice (auto-generates actions and reducers), configureStore (sensible defaults), and RTK Query (data fetching and caching). If you need Redux, use RTK — never plain Redux in 2024.

🔍 What Is It?

Redux Toolkit is a set of utilities that make Redux practical:

  • →createSlice — defines state, reducers, and actions in one place (uses Immer under the hood, so you can "mutate" state directly)
  • →configureStore — sets up the Redux store with Redux DevTools and middleware automatically
  • →createAsyncThunk — handles async operations (API calls) with automatic pending/fulfilled/rejected action dispatching
  • →RTK Query — a full data fetching and caching solution built on top of Redux

Classic Redux required separate files for action types, action creators, and reducers. RTK collapses all of this into a single slice file.

🎯 Why Do We Need It?

Context API has performance limitations for frequently-changing shared state. Zustand is great for medium complexity. Redux shines when:

  • →Large teams need strict conventions and predictable patterns
  • →Complex state with many interdependent slices
  • →Time-travel debugging is needed (Redux DevTools)
  • →Audit trails — every state change is a logged, named action
  • →RTK Query provides a unified data fetching layer with caching, invalidation, and optimistic updates

🏗️ What Happens in Real Projects?

Redux Toolkit is common in:

  • →Banking and insurance — complex claim/policy workflows with many steps and interdependent state
  • →E-commerce — cart, checkout, inventory, user preferences all in one store
  • →Healthcare — patient records, appointment scheduling, complex forms
  • →Large SaaS dashboards — filters, pagination, sorting, selection all managed globally

RTK Query is increasingly replacing React Query in Redux-heavy apps, providing a single solution for both client and server state.

⚙️ How It Works

Store setup:

One store holds the entire app state as a plain JS object. The store is created once and provided to the app via the Redux <Provider>.

Slices:

Each feature gets a "slice" — a self-contained piece of state with its own reducers and actions.

Dispatch and select:

  • →useDispatch() — returns the dispatch function to trigger actions
  • →useSelector(selector) — reads from the store and re-renders when the selected value changes

Immer:

RTK uses Immer internally, so reducers can write state.value = newValue directly instead of returning a new object. Immer converts this to an immutable update under the hood.

💻 Example

Redux Toolkit in a Claims Management App — Slice definition, async thunks, and component integration.

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

interface Claim {
  id: string;
  policyNumber: string;
  status: 'open' | 'in-review' | 'approved' | 'rejected';
  amount: number;
}

interface ClaimsState {
  items: Claim[];
  selectedId: string | null;
  loading: boolean;
  error: string | null;
}

const initialState: ClaimsState = {
  items: [],
  selectedId: null,
  loading: false,
  error: null,
};

// Async thunk — handles API call with auto pending/fulfilled/rejected
export const fetchClaims = createAsyncThunk('claims/fetchAll', async (userId: string) => {
  const res = await fetch(`/api/users/${userId}/claims`);
  if (!res.ok) throw new Error('Failed to fetch claims');
  return res.json() as Promise<Claim[]>;
});

export const claimsSlice = createSlice({
  name: 'claims',
  initialState,
  reducers: {
    selectClaim(state, action: PayloadAction<string>) {
      state.selectedId = action.payload; // Immer allows direct mutation
    },
    clearSelection(state) {
      state.selectedId = null;
    },
    updateClaimStatus(state, action: PayloadAction<{ id: string; status: Claim['status'] }>) {
      const claim = state.items.find(c => c.id === action.payload.id);
      if (claim) claim.status = action.payload.status;
    },
  },
  extraReducers: (builder) => {
    builder
      .addCase(fetchClaims.pending, (state) => {
        state.loading = true;
        state.error = null;
      })
      .addCase(fetchClaims.fulfilled, (state, action) => {
        state.loading = false;
        state.items = action.payload;
      })
      .addCase(fetchClaims.rejected, (state, action) => {
        state.loading = false;
        state.error = action.error.message ?? 'Unknown error';
      });
  },
});

export const { selectClaim, clearSelection, updateClaimStatus } = claimsSlice.actions;

🤔 Common Confusions

❓ Should I use Redux or Zustand in 2024?

For most apps: Zustand. It is simpler, has less boilerplate, and handles 90% of state management needs. Use Redux Toolkit when: (1) you need strict conventions for a large team, (2) you need RTK Query's advanced caching features, (3) you need time-travel debugging, (4) the app already uses Redux.

❓ Can I mutate state directly in RTK reducers?

Yes — inside createSlice reducers only. RTK uses Immer, which intercepts the mutations and produces a new immutable state object. Outside of Immer-wrapped reducers (e.g., in component code), you must never mutate Redux state directly.

❓ What is the difference between Redux Toolkit and RTK Query?

Redux Toolkit is the core library for state management (slices, store, thunks). RTK Query is an optional add-on for data fetching and caching built on top of RTK. RTK Query competes with React Query — if you are already using Redux, RTK Query gives you a unified solution.

🚨 Common Production Issues

**Performance: Large selectors without memoisation** — useSelector runs on every store update. For derived data (filtered lists, computed totals), use createSelector (Reselect) to memoise.

**Over-fetching in RTK Query** — Multiple components subscribing to the same endpoint trigger one shared request. Ensure endpoints are defined with the same cache key for proper deduplication.

**Normalisation** — Storing nested data (claims with nested policies) causes complex update logic. Use createEntityAdapter or normalise data at the API layer.

**Bundle size** — Redux + RTK + React-Redux adds ~20KB. For small apps, this is unnecessary overhead. Use Zustand (1KB) instead.

✅ Best Practices

Use typed hooks (useAppDispatch, useAppSelector) everywhere — never raw hooks

One slice per feature — co-locate with the feature folder

Use createSelector for derived/filtered data to prevent unnecessary re-renders

Use createEntityAdapter for collections of items (normalised state)

Use RTK Query for all API calls if you are already using Redux

Never put non-serialisable values in the store (functions, class instances, Promises)

🏛️ Architect's Note

Senior Architect's Perspective

Redux is a powerful tool that many teams reach for too early. The overhead of actions, reducers, selectors, and thunks is justified when the app is genuinely complex and the team is large enough to benefit from strict conventions. For a team of 2–5 developers building a typical SaaS product, Zustand will get you 90% of the way with 10% of the complexity. Reserve Redux for apps where the state logic itself is the hard problem — complex multi-step workflows, real-time collaboration, or when RTK Query's caching features are genuinely needed.

💬 Frequently Asked Questions

Do I need to use Redux DevTools?

Not required, but highly recommended during development. configureStore enables DevTools automatically. It lets you inspect every dispatched action, view the state at any point in time, and replay actions — invaluable for debugging complex state issues.

What is createEntityAdapter?

createEntityAdapter normalises collections into a { ids: [], entities: {} } structure and provides pre-built reducers (addOne, updateOne, removeOne, setAll) and selectors. Use it for any slice that manages a list of items with IDs — it eliminates repetitive CRUD reducer code.

🎯 Key Takeaways

1RTK eliminates classic Redux boilerplate with createSlice and configureStore
2createSlice auto-generates action creators and uses Immer for immutable updates
3createAsyncThunk handles API calls with automatic pending/fulfilled/rejected states
4Always use typed hooks (useAppDispatch, useAppSelector) for TypeScript safety
5Use createSelector for memoised derived data
6For most apps, Zustand is simpler — use Redux when complexity justifies it

🎯 Practice this topic in interviews

↗ Where this topic fits

Sources

Sources / References

Use these React references to verify the concept in official docs and continue into the most relevant SplashRide pages.

Related SplashRide pages

SplashRide React learning path

Browse the full React topic collection and learning order on SplashRide.

React Interview Prep

Practice React interview questions and scenario-based answers.

Frontend Engineer Career Path

A beginner-friendly journey through web foundations, React, production frontend engineering, and interview preparation.

Python Developer Career Path

A practical route through Python fundamentals, automation, backend basics, testing, projects, interview preparation, and career growth.

Context API

Master React Context — the built-in solution for sharing state across the component tree without prop drilling. Learn when to use it, when not to, and how senior engineers implement it in production.

Zustand

Master Zustand — the lightweight, modern state management library that most teams should use instead of Redux. Learn store design, selectors, persistence, and enterprise patterns.