iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

Redux Toolkit

Redux Toolkit (RTK): the modern Redux. Slices, createAsyncThunk, RTK Query, less boilerplate.

React — Redux Toolkit

EXAMPLE
// Install: npm install @reduxjs/toolkit react-redux
import { createSlice, configureStore } from '@reduxjs/toolkit';
import { Provider, useDispatch, useSelector } from 'react-redux';

// ===== Slice =====
const cartSlice = createSlice({
  name: 'cart',
  initialState: { items: [] },
  reducers: {
    add: (state, action) => { state.items.push(action.payload); },     // Immer makes this OK
    remove: (state, action) => { state.items = state.items.filter(i => i.id !== action.payload); },
    clear: () => ({ items: [] }),
  },
});

export const { add, remove, clear } = cartSlice.actions;

// ===== Store =====
const store = configureStore({
  reducer: { cart: cartSlice.reducer },
});
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;

// ===== App =====
function App() {
  return <Provider store={store}><Cart /></Provider>;
}

function Cart() {
  const items = useSelector((s: RootState) => s.cart.items);
  const dispatch = useDispatch<AppDispatch>();
  return (
    <div>
      <p>{items.length} items</p>
      <button onClick={() => dispatch(add({ id: Date.now(), name: 'Widget' }))}>Add</button>
    </div>
  );
}

// ===== Async with createAsyncThunk =====
import { createAsyncThunk } from '@reduxjs/toolkit';

export const loadUsers = createAsyncThunk('users/load', async () => {
  const r = await fetch('/api/users');
  if (!r.ok) throw new Error('failed');
  return r.json();
});

const usersSlice = createSlice({
  name: 'users',
  initialState: { items: [], loading: false, error: null },
  reducers: {},
  extraReducers: (builder) => {
    builder
      .addCase(loadUsers.pending, (s) => { s.loading = true; })
      .addCase(loadUsers.fulfilled, (s, a) => { s.loading = false; s.items = a.payload; })
      .addCase(loadUsers.rejected, (s, a) => { s.loading = false; s.error = a.error.message; });
  },
});

// ===== RTK Query (built-in fetch + cache) =====
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';

export const api = createApi({
  reducerPath: 'api',
  baseQuery: fetchBaseQuery({ baseUrl: '/api' }),
  endpoints: (build) => ({
    listUsers: build.query<User[], void>({ query: () => 'users' }),
    addUser: build.mutation<User, Partial<User>>({
      query: (body) => ({ url: 'users', method: 'POST', body }),
    }),
  }),
});

export const { useListUsersQuery, useAddUserMutation } = api;

// In a component:
function Users() {
  const { data, isLoading } = useListUsersQuery();
  const [addUser] = useAddUserMutation();
  // ...
}

// ===== Patterns =====
// - One slice per feature
// - Use createSlice / createAsyncThunk; no manual action types
// - RTK Query for server state (replaces fetch + cache code)
// - Typed hooks (useAppDispatch, useAppSelector)

// ===== Pitfalls =====
// - Direct mutation works in slices (Immer) but feels wrong if you're used to plain Redux
// - Over-fetching with RTK Query without tags / invalidation
// - Selecting whole state -> re-renders on every change
// - Boilerplate creeping back if you ignore RTK and write old-style Redux

Why it matters

Redux Toolkit removes 90% of the old Redux boilerplate. createSlice + createAsyncThunk + RTK Query is the modern shape. Reach for it when you need middleware, time-travel debugging, or RTK Query for server state — otherwise Zustand often wins on simplicity.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
import { configureStore, createSlice } from '@reduxjs/toolkit';
const counter = createSlice({ name: 'counter', initialState: 0, reducers: { inc: s => s + 1 } });
export const store = configureStore({ reducer: counter.reducer });
Try it Yourself »

Discussion

Loading…