Using Redux Toolkit’s createSlice to Cut Boilerplate in React State Management
Learn how createSlice bundles reducers, actions, and initial state, leverages Immer for safe mutations, and integrates with DevTools for clearer debugging.
05 Sept 2026, 23:13 UTC

The problem: scattered action types and repetitive reducers
When building a medium‑size React app with plain Redux, each feature often requires:
- Defining action type constants.
- Writing action creator functions.
- Implementing a reducer switch statement that handles those types.
- Exporting the reducer and wiring it into the store.
As the number of features grows, this boilerplate spreads across files, makes refactoring error‑prone, and obscures the relationship between actions and state updates.
Thesis: createSlice consolidates the three concerns
Redux Toolkit’s createSlice API solves the above by generating action creators and action types directly from the reducer case definitions you write. It also wraps the reducer logic with Immer, letting you write "mutating" code that is safely turned into immutable updates.
How createSlice works
- You pass an object with
name,initialState, and areducersmap. - For each reducer case,
createSlicecreates an action creator whose type issliceName/caseName. - The reducer function receives the state and action; thanks to Immer, you can modify the state parameter directly and the library returns a new immutable state.
Worked example: a counter feature
Below is a minimal, self‑contained example that shows the slice definition, store setup, and a React component that uses the generated actions.
1. Define the slice
import { createSlice } from '@reduxjs/toolkit'
const counterSlice = createSlice({
name: 'counter',
initialState: { value: 0 },
reducers: {
increment: (state) => {
state.value += 1 // Immer converts this to an immutable update
},
decrement: (state) => {
state.value -= 1
},
reset: (state) => {
state.value = 0
}
},
})
export const { increment, decrement, reset } = counterSlice.actions
export default counterSlice.reducer
2. Configure the store
import { configureStore } from '@reduxjs/toolkit'
import counterReducer from './counterSlice'
const store = configureStore({
reducer: {
counter: counterReducer
},
})
export default store
3. Use the slice in a React component
import React from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { increment, decrement, reset } from './counterSlice'
function Counter() {
const dispatch = useDispatch()
const count = useSelector(state => state.counter.value)
return (
Counter: {count}
dispatch(increment())}>+
dispatch(decrement())}>-
dispatch(reset())}>Reset
)
}
export default Counter
When you click the buttons, the dispatched actions flow through the slice’s reducers, and the UI updates accordingly. Open Redux DevTools to see each action logged with its payload (none in this case) and the resulting state change.
Trade‑off and limitation
While createSlice reduces boilerplate, it introduces a dependency on Redux Toolkit. Migrating away from RTK would require rewriting each slice into plain Redux logic or another state‑management library. Additionally, because Immer lets you write mutating code, it’s easy to accidentally place side‑effects (e.g., API calls) inside a reducer. Such side‑effects break reducer purity and can cause unpredictable state updates or middleware loops. The rule is: keep reducers pure; move async logic to thunks, sagas, or observables.
How to verify the behavior
- DevTools check: After installing
redux-devtools-extension(or using the built‑in DevTools support in RTK), open the DevTools panel, dispatch an action, and confirm that the state tree updates as expected. - Subscription test: In a test environment, call
store.subscribe(() => console.log(store.getState()))and dispatch an action; verify the logged state matches the intended mutation. - Unit test: Import the slice reducer, invoke it with a known action (e.g.,
counterSlice.actions.increment()), and assert that the returned state equals{ value: 1 }when starting from{ value: 0 }.
Closing: adopt createSlice for feature‑scoped state
If you’re starting a new React project or refactoring existing Redux code, begin by grouping related state, actions, and reducers into a slice with createSlice. This keeps each feature self‑contained, reduces the chance of mismatched action types, and gives you immediate access to Immer‑powered immutable updates and DevTools integration. Remember to keep reducers free of side effects and to test the reducer logic in isolation to guarantee predictable state transitions.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.