Redux Toolkit
Modern Redux state management for Next.js
🗄️ What is Redux Toolkit?
Redux Toolkit is the official, recommended way to write Redux logic. It simplifies store setup, reduces boilerplate code, and includes powerful tools like createSlice and createAsyncThunk for managing global application state efficiently.
// Simple Redux Toolkit 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 }
}
})
export const { increment, decrement } = counterSlice.actions
Key Redux Toolkit Features
Store Setup
Easy store configuration
configureStore({
reducer: {
counter: counterReducer
}
})
Slices
Combine reducers and actions
createSlice({
name: 'user',
initialState,
reducers: {}
})
Async Thunks
Handle async operations
createAsyncThunk(
'users/fetch',
async () => fetchUsers()
)
Selectors
Access state efficiently
useSelector(
state => state.counter.value
)
🔹 Setup Redux Toolkit Store
Create a Redux store using configureStore and wrap your Next.js app with the Provider. This makes the store available to all components in your application.
// lib/store.js
import { configureStore } from '@reduxjs/toolkit'
import counterReducer from './features/counterSlice'
export const store = configureStore({
reducer: {
counter: counterReducer
}
})
// app/providers.jsx
'use client'
import { Provider } from 'react-redux'
import { store } from '@/lib/store'
export default function Providers({ children }) {
return <Provider store={store}>{children}</Provider>
}
// app/layout.jsx
import Providers from './providers'
export default function RootLayout({ children }) {
return (
<html>
<body>
<Providers>{children}</Providers>
</body>
</html>
)
}
🔹 Create a Slice
Slices combine state, reducers, and actions in one place. Use createSlice to define your state logic with automatic action creators and immutable updates using Immer.
// lib/features/counterSlice.js
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
State Structure:
{ counter: { value: 0 } }
🔹 Using Redux in Components
Access state with useSelector and dispatch actions with useDispatch. These hooks connect your components to the Redux store for reading and updating state.
// components/Counter.jsx
'use client'
import { useSelector, useDispatch } from 'react-redux'
import { increment, decrement, incrementByAmount } from '@/lib/features/counterSlice'
export default function Counter() {
const count = useSelector((state) => state.counter.value)
const dispatch = useDispatch()
return (
<div>
<h2>Count: {count}</h2>
<button onClick={() => dispatch(increment())}>
Increment
</button>
<button onClick={() => dispatch(decrement())}>
Decrement
</button>
<button onClick={() => dispatch(incrementByAmount(5))}>
Add 5
</button>
</div>
)
}
Output:
Count: 0
🔹 Async Operations with createAsyncThunk
Handle API calls and async logic with createAsyncThunk. It automatically dispatches pending, fulfilled, and rejected actions based on your async function's lifecycle.
// lib/features/userSlice.js
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'
export const fetchUsers = createAsyncThunk(
'users/fetchUsers',
async () => {
const response = await fetch('/api/users')
return response.json()
}
)
const userSlice = createSlice({
name: 'users',
initialState: {
list: [],
status: 'idle',
error: null
},
reducers: {},
extraReducers: (builder) => {
builder
.addCase(fetchUsers.pending, (state) => {
state.status = 'loading'
})
.addCase(fetchUsers.fulfilled, (state, action) => {
state.status = 'succeeded'
state.list = action.payload
})
.addCase(fetchUsers.rejected, (state, action) => {
state.status = 'failed'
state.error = action.error.message
})
}
})
export default userSlice.reducer
🔹 Using Async Thunks in Components
Dispatch async thunks like regular actions. The component can track loading states and handle errors automatically through the Redux state.
// components/UserList.jsx
'use client'
import { useEffect } from 'react'
import { useSelector, useDispatch } from 'react-redux'
import { fetchUsers } from '@/lib/features/userSlice'
export default function UserList() {
const dispatch = useDispatch()
const { list, status, error } = useSelector((state) => state.users)
useEffect(() => {
if (status === 'idle') {
dispatch(fetchUsers())
}
}, [status, dispatch])
if (status === 'loading') return <div>Loading...</div>
if (status === 'failed') return <div>Error: {error}</div>
return (
<ul>
{list.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
)
}