Learn Redux - createSlice & Reducers
Episode 4 of 23

Learn Redux - createSlice & Reducers

This episode teaches createSlice: defining name, initialState, and reducers in one place, with automatically generated action creators and action types. You'll also write reducers using Immer draft mutation and handle external actions through extraReducers.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Introduction

In episode 3 you already had a live store, but it was empty. Now we fill that store with a slice — a piece of state along with its reducer and actions. createSlice is the star of Redux Toolkit: one function that removes nearly all classic Redux boilerplate.

Episode 4 covers three things: how to define a slice with name, initialState, and reducers; how Immer makes writing reducers feel like direct mutation; and how extraReducers handles actions coming from outside the slice — including the async actions we'll learn about in episode 6.

Getting to Know createSlice

name, initialState, and reducers

createSlice accepts a single configuration object with three main properties:

JSYour first counter slice
import { createSlice } from "@reduxjs/toolkit"
 
const counterSlice = createSlice({
  name: "counter",
  initialState: { count: 0 },
  reducers: {
    increment(state) {
      state.count += 1
    },
    decrement(state) {
      state.count -= 1
    },
    setTo(state, action) {
      state.count = action.payload
    },
  },
})
 
export const { increment, decrement, setTo } = counterSlice.actions
export default counterSlice.reducer
  • name becomes the prefix of every action type, e.g. counter/increment.
  • initialState is the initial shape of the slice state.
  • reducers holds the functions that handle state changes.

Automatic Action Creators and Action Types

The biggest advantage of createSlice: every key in reducers automatically generates an action creator and an action type.

JSAutomatically generated actions
console.log(increment())
console.log(setTo(5))

increment() produces { type: "counter/increment" }, and setTo(5) produces { type: "counter/setTo", payload: 5 }. You no longer need to write action constants and action creators by hand like in the classic Redux era. The whole slice is registered in the store:

JSRegister the slice in the store
import { configureStore } from "@reduxjs/toolkit"
import counterReducer from "../features/counter/counterSlice"
 
export const store = configureStore({
  reducer: {
    counter: counterReducer,
  },
})

counter: counterReducer mounts the counter slice reducer into the state.counter slot of the store. This slot name will be used by selectors in episode 5.

Writing Reducers with Immer

Safe Draft Mutation

Notice that inside the reducer we write state.count += 1 — it looks like mutation, and that's intentional. createSlice wraps every reducer with Immer, so the state the function receives is a temporary draft.

JSReducer with draft mutation
reducers: {
  increment(state) {
    state.count += 1
  },
}

Immer records all changes to the draft, then produces a fully immutable new state object behind the scenes. Redux DevTools will display the change as a clear diff — without Immer, you'd have to write { count: state.count + 1 } by hand.

Warning

Never both return a new value AND mutate the draft in the same reducer. Pick one pattern: mutate the draft only, or return a new object only. Combining both produces undefined behavior.

When to Return Manually

There are cases where draft mutation isn't practical: replacing the entire state or performing heavy data transforms. For these, returning a new object is still allowed:

JSReturning new state manually
reducers: {
  reset(state) {
    return { count: 0 }
  },
}

return { count: 0 } replaces the entire slice state at once — without spreading the old draft. Both styles are valid; use whichever reads more clearly.

extraReducers

Handling Actions from Outside the Slice

extraReducers is used to respond to actions that aren't part of this slice. Typical examples are actions from createAsyncThunk (episode 6) or actions from another slice:

JSHandling external actions
const cartSlice = createSlice({
  name: "cart",
  initialState: { items: [], lastAction: null },
  reducers: {},
  extraReducers: (builder) => {
    builder.addCase("user/logout", (state) => {
      state.items = []
    })
  },
})

builder.addCase registers one handler per action type. state.items = [] empties the cart every time a user logs out — cross-feature synchronization without importing each other's reducers.

addCase, addMatcher, and addDefaultCase

extraReducers supports three builder forms:

  • addCase(actionCreator, reducer): for a specific action.
  • addMatcher(predicate, reducer): for actions matching a predicate.
  • addDefaultCase(reducer): fallback for unrecognized actions.
JSMatcher and default case
extraReducers: (builder) => {
  builder
    .addMatcher((action) => action.type.endsWith("/pending"), (state) => {
      state.loading = true
    })
    .addDefaultCase((state) => state)
}

addMatcher is very useful for catching action patterns, for example every action ending in pending. This pattern is widely used when working with thunk lifecycles.

Conclusion

Episode 4 introduces the heart of modern Redux state writing: one createSlice produces the reducer, action creators, and action types all at once. Immer makes reducers feel like direct mutation, and extraReducers opens the door to cross-slice collaboration.

Key takeaways:

  • createSlice accepts name, initialState, and reducers.
  • Every reducer automatically generates an action creator and action type.
  • Reducers use draft mutation guaranteed safe by Immer.
  • Don't mix draft mutation with manual return in a single reducer.
  • extraReducers with builder.addCase for external actions.
  • addMatcher and addDefaultCase for broader action patterns.

In the next episode, episode 5, you'll read that state from components with selectors and hooks — using useSelector and useDispatch, understanding selectors as pure functions, and keeping performance in check with shallowEqual when selecting complex data.

Learn Redux - createSlice & Reducers | Learn Redux