Skip to main content

Creating Slices with createSlice

createSlice is the standard approach for writing Redux logic. It generates action creators and action types automatically based on reducer names.

Basic Slice​

import { createSlice } from '@reduxjs/toolkit'

const counterSlice = createSlice({
name: 'counter',
initialState: {
value: 0,
},
reducers: {
increment: (state) => {
state.value += 1
},
decrement: (state) => {
state.value -= 1
},
incrementByAmount: (state, action) => {
state.value += action.payload
},
},
})

export const { increment, decrement, incrementByAmount } = counterSlice.actions
export default counterSlice.reducer

Key Features​

Immer Integration​

  • Mutative syntax: Write code that looks like it mutates state
  • Immutable updates: Immer handles immutability automatically
  • Simplified logic: No need for spread operators

Auto-generated Actions​

// Actions are automatically created
counterSlice.actions.increment() // { type: 'counter/increment' }
counterSlice.actions.incrementByAmount(5) // { type: 'counter/incrementByAmount', payload: 5 }

Complex State Examples​

Object State​

const userSlice = createSlice({
name: 'user',
initialState: {
profile: null,
isLoading: false,
error: null,
},
reducers: {
setUser: (state, action) => {
state.profile = action.payload
},
updateProfile: (state, action) => {
Object.assign(state.profile, action.payload)
},
setLoading: (state, action) => {
state.isLoading = action.payload
},
setError: (state, action) => {
state.error = action.payload
},
},
})

Array State​

const todosSlice = createSlice({
name: 'todos',
initialState: [],
reducers: {
addTodo: (state, action) => {
state.push({
id: action.payload.id,
text: action.payload.text,
completed: false,
})
},
toggleTodo: (state, action) => {
const todo = state.find(todo => todo.id === action.payload)
if (todo) {
todo.completed = !todo.completed
}
},
removeTodo: (state, action) => {
return state.filter(todo => todo.id !== action.payload)
},
},
})

Prepare Callbacks​

Customize action payloads with prepare callbacks:

const postsSlice = createSlice({
name: 'posts',
initialState: [],
reducers: {
addPost: {
reducer: (state, action) => {
state.push(action.payload)
},
prepare: (title, content) => {
return {
payload: {
id: nanoid(),
title,
content,
createdAt: new Date().toISOString(),
},
}
},
},
},
})

ExtraReducers​

Handle actions from other slices or async thunks:

const postsSlice = createSlice({
name: 'posts',
initialState: {
posts: [],
status: 'idle',
},
reducers: {
// Regular reducers
},
extraReducers: (builder) => {
builder
.addCase(fetchPosts.pending, (state) => {
state.status = 'loading'
})
.addCase(fetchPosts.fulfilled, (state, action) => {
state.status = 'succeeded'
state.posts = action.payload
})
.addCase(fetchPosts.rejected, (state, action) => {
state.status = 'failed'
state.error = action.error.message
})
},
})

TypeScript with createSlice​

interface CounterState {
value: number
}

const initialState: CounterState = {
value: 0,
}

const counterSlice = createSlice({
name: 'counter',
initialState,
reducers: {
increment: (state) => {
state.value += 1
},
incrementByAmount: (state, action: PayloadAction<number>) => {
state.value += action.payload
},
},
})

Best Practices​

  1. One slice per feature
  2. Use descriptive names
  3. Keep reducers simple
  4. Use prepare callbacks for complex payloads
  5. Leverage TypeScript for type safety