Slice interface properties
Slice<State, CaseReducers, Name> has the following properties: actions (CaseReducerActions<CaseReducers>), caseReducers (SliceDefinedCaseReducers<CaseReducers>), getInitialState (() => State), name (Name), reducer (Reducer<State>).
CreateSliceOptions interface properties
CreateSliceOptions<State, CR, Name> has the following properties: extraReducers (CaseReducers<NoInfer<State>, any> | ((builder: ActionReducerMapBuilder<NoInfer<State>>) => void), optional), initialState (State | (() => State), required), name (Name, required), reducers (ValidateSliceCaseReducers<State, CR>, required).
createSlice function signature and return type
createSlice is a generic function with signature: createSlice<State, CaseReducers extends SliceCaseReducers<State>, Name extends string = string>(options: CreateSliceOptions<State, CaseReducers, Name>): Slice<State, CaseReducers, Name>. It accepts CreateSliceOptions and returns a Slice object.
Migrate reducers incrementally as you touch them
When a legacy reducer requires editing, migrate that reducer to createSlice at that time instead of adding more legacy code to it. This prevents accumulating new legacy patterns during the migration period.
createSlice example with multiple reducers and typed actions
Example createSlice migration with typed state and actions: createSlice({ name: 'todos', initialState: { items: [] as { id: string; text: string; completed: boolean }[] }, reducers: { todoAdded(state, action: { payload: { id: string; text: string } }) { state.items.push({ ...action.payload, completed: false }) }, todoToggled(state, action: { payload: { id: string } }) { const todo = state.items.find((item) => item.id === action.payload.id); if (todo) { todo.completed = !todo.completed } } } }). Shows Immer-based mutation directly on state.
Selector factory pattern instead of createSlice.selectors
If a selector depends on caller-specific arguments and must be memoized separately for each caller, create a selector factory outside of createSlice.selectors. createSlice.selectors provides a single selector instance, not a factory. Use selector factories when memoization must be per-caller rather than global.
getSelectors for alternate mounting points
Call getSelectors on a slice with a selector function to remap selectors when the slice is not mounted at its default reducerPath. The selector function receives the root state and returns the slice state. Example: counterSlice.getSelectors((state) => state.customCounter) allows selectValue to work when the counter slice is mounted at state.customCounter instead of the default location.
RTK 2 extraReducers builder pattern required
RTK 2 removed object-form extraReducers syntax. Use builder form: extraReducers: (builder) => { builder.addCase(asyncAction.fulfilled, (state, action) => { ... }) }. The old object syntax with computed property keys is no longer supported.
Avoid hand-written switch reducers in RTK applications
Hand-written switch statement reducers should not be the default in Redux Toolkit code. Use createSlice instead, which handles action creation, reducer bundling, and Immer integration. Hand-written reducers are an escape hatch only for proven bottlenecks.
Slice selectors property in createSlice
The createSlice function accepts a selectors property that defines memoized selector functions scoped to that slice. Selectors are extracted via slice.selectors and keep state-location knowledge next to the slice definition.
Slice reducers receive create object with builder methods
When buildCreateSlice is configured with asyncThunkCreator, the reducers property becomes a function receiving a create object. The create object provides methods like reducer() for simple reducers and asyncThunk() for async lifecycle handlers.
Do not let slice boundaries fossilize
Avoid keeping unrelated data welded together in a single slice indefinitely. When unrelated data is in the same slice, every change point gets noisier and harder to manage. Split or merge slices as actual access patterns demand over time.
Re-size slices when access patterns change
Revisit slice boundaries over time as the application evolves. Unrelated data should be split into separate slices, and data that is constantly stitched together in every component should be moved closer together in the state tree.
createSlice uses Immer to enable immutable updates with mutating syntax
The createSlice function lets you write reducers that use the Immer library to enable writing immutable updates using mutating JavaScript syntax like state.value = 123, with no spreads needed. It also automatically generates action creator functions for each reducer and generates action type strings internally based on your reducer's names. It works great with TypeScript.
Example: Legacy hand-written Redux reducer with switch statement
Prior to Redux Toolkit, reducers were typically written with a switch statement and manual updates, along with hand-written action creators and action type constants. Example:
const ADD_TODO = 'ADD_TODO'
const TODO_TOGGLED = 'TODO_TOGGLED'
export const addTodo = (text) => ({
type: ADD_TODO,
payload: { text, id: nanoid() },
})
export const todoToggled = (id) => ({
type: TODO_TOGGLED,
payload: { id },
})
export const todosReducer = (state = [], action) => {
switch (action.type) {
case ADD_TODO:
return state.concat({
id: action.payload.id,
text: action.payload.text,
completed: false,
})
case TODO_TOGGLED:
return state.map((todo) => {
if (todo.id !== action.payload.id) return todo
return {
...todo,
completed: !todo.completed,
}
})
default:
return state
}
}
Example: Modern Redux with createSlice
With Redux Toolkit createSlice, the same todos reducer can be written much more concisely. Example:
import { createSlice } from '@reduxjs/toolkit'
const todosSlice = createSlice({
name: 'todos',
initialState: [],
reducers: {
todoAdded(state, action) {
state.push({
id: action.payload.id,
text: action.payload.text,
completed: false,
})
},
todoToggled(state, action) {
const todo = state.find((todo) => todo.id === action.payload)
todo.completed = !todo.completed
},
},
})
export const { todoAdded, todoToggled } = todosSlice.actions
export default todosSlice.reducer
createReducer accepts action types lookup table with Immer
createReducer() lets you supply a lookup table of action types to case reducer functions, rather than writing switch statements. It automatically uses the Immer library to let you write simpler immutable updates with normal mutative code, like state.todos[3].completed = true.
createSlice combines createReducer and createAction
createSlice() combines createReducer() + createAction(). It accepts an object of reducer functions, a slice name, and an initial state value, and automatically generates a slice reducer with corresponding action creators and action types.