Simplifying Redux Logic with Redux Toolkit’s createSlice
Learn how createSlice reduces boilerplate, generates action creators, and lets you write mutable‑style reducers that stay immutable via Immer.
13 Jul 2026, 11:27 UTC

Problem: Repetitive Redux boilerplate slows feature work
When you write plain Redux reducers you must:
- Declare action type constants
- Write action creator functions
- Implement a
switchstatement that checksaction.type - Carefully return new state objects to preserve immutability
As the state shape grows, this pattern becomes noisy and error‑prone. A missed return or a typo in a type constant can silently break updates.
Thesis: Redux Toolkit’s createSlice removes the boilerplate while keeping Redux’s predictable flow
createSlice lets you describe a slice of state with an object of reducer functions. It automatically:
- Generates action type strings in the form
sliceName/actionName - Creates matching action creators
- Wraps each reducer with Immer, so you can write "mutating" code that is safely turned into immutable updates
The result is less code to maintain and a clearer connection between actions and the state they modify.
Worked example: A todo slice
The following snippets show how to define a slice, configure the store, and use it in a React component. Adjust the file paths to match your project structure.
1. Define the slice (src/features/todos/todoSlice.js)
import { createSlice } from '@reduxjs/toolkit'
const todoSlice = createSlice({
name: 'todos',
initialState: [],
reducers: {
addTodo: (state, action) => {
// Immer lets us push directly; the draft is turned into an immutable new state
state.push({ id: Date.now(), text: action.payload, completed: false })
},
toggleTodo: (state, action) => {
const todo = state.find(t => t.id === action.payload)
if (todo) todo.completed = !todo.completed
},
removeTodo: (state, action) => {
return state.filter(t => t.id !== action.payload)
}
}
})
export const { addTodo, toggleTodo, removeTodo } = todoSlice.actions
export default todoSlice.reducer
2. Add the reducer to the store (src/app/store.js)
import { configureStore } from '@reduxjs/toolkit'
import todoReducer from '../features/todos/todoSlice'
export const store = configureStore({
reducer: {
todos: todoReducer
},
// devTools is enabled by default in development
})
3. Use the slice in a React component (src/components/TodoList.js)
import { useSelector, useDispatch } from 'react-redux'
import { addTodo, toggleTodo, removeTodo } from '../features/todos/todoSlice'
import { v4 as uuidv4 } from 'uuid'
export default function TodoList() {
const dispatch = useDispatch()
const todos = useSelector(state => state.todos)
const handleAdd = e => {
e.preventDefault()
const text = e.target.elements.todo.value.trim()
if (text) {
dispatch(addTodo(text))
e.target.reset()
}
}
return (
Add
{todos.map(todo => (
-
dispatch(toggleTodo(todo.id))}
>
{todo.text}
dispatch(removeTodo(todo.id))}>✕
))}
)
}
When you run the app (e.g., npm start in a Create React App project with @reduxjs/toolkit ≥ 1.8.0), typing a todo and pressing Add dispatches addTodo. The action type logged in Redux DevTools will be todos/addTodo, confirming the automatic naming.
Trade‑off and guidance
While createSlice cuts boilerplate, it introduces two considerations:
- Action names are coupled to the slice name. If you rename the slice, all generated action types change. Document the generated types (e.g., in a comment) or use
todoSlice.actionTypesif you need to reference them elsewhere. - Underlying Redux mechanics are hidden. New developers may not see the
switchstatement or the action constants. Encourage learning the basics of Redux alongside RTK so the team can debug edge cases (e.g., when you need custom action types).
For asynchronous logic (fetching data, timers), pair createSlice with createAsyncThunk or middleware like redux‑thunk. The slice only handles synchronous reducers.
Actionable closing
Start by converting one small feature’s reducer to a slice:
- Copy the initial state and reducer logic into a
createSlicecall. - Export the generated actions and reducer.
- Replace the old reducer in the store with the slice reducer.
- Update components to import the new action creators.
- Verify that the UI behaves the same and that Redux DevTools shows the expected
sliceName/actionNamepatterns.
If the slice grows beyond ~20 reducers, consider splitting it into domain‑specific slices (e.g., todos and todoFilters) to keep each file focused. Over time, migrate legacy reducers incrementally; the new slices will coexist with existing combineReducers logic without breaking the store.
By adopting createSlice you keep the predictability of Redux while reducing the repetitive code that slows down feature development.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.