State management has evolved significantly in modern React applications. While Redux has long been a powerful solution, its earlier boilerplate-heavy patterns made it intimidating for many developers. Redux Toolkit (RTK) was introduced to solve those problems—and today, it is the official, recommended way to write Redux logic.
In this article, you’ll get a comprehensive and up-to-date explanation of Redux Toolkit, how it works, why it exists, and how to use it effectively in modern React applications.
Table of Contents
- 1. Why Redux Toolkit Exists
- 2. What Is Redux Toolkit?
- 3. Installing Redux Toolkit
- 4. Core Concepts
- 5. Redux Toolkit with React
- 6. RTK Query (Built-in Data Fetching)
- 7. Folder Structure Best Practices
- 8. Performance and Optimization
- 9. Common Mistakes to Avoid
- 10. When to Use Redux Toolkit
- 11. My Thoughts
1. Why Redux Toolkit Exists
Classic Redux required:
- Manual action type constants
- Action creators
- Switch statements in reducers
- Immutable update logic by hand
- Separate middleware setup
This led to excessive boilerplate and developer fatigue.
Redux Toolkit was introduced by the Redux core team to:
- Simplify Redux setup
- Reduce boilerplate
- Enforce best practices
- Provide built-in support for async logic
- Improve developer experience
As of today, Redux Toolkit 2.x is the standard way to write Redux applications.
2. What Is Redux Toolkit?
Redux Toolkit is an official package that wraps Redux and provides utilities to simplify store setup and reducer logic.
It includes:
configureStorecreateSlicecreateAsyncThunkcreateEntityAdaptercreateSelector- Built-in Immer support
- Built-in DevTools configuration
- RTK Query for data fetching
It eliminates almost all manual Redux boilerplate.
3. Installing Redux Toolkit
npm install @reduxjs/toolkit react-redux
Or with Yarn:
yarn add @reduxjs/toolkit react-redux
Redux Toolkit already includes Redux internally, so you do not need to install redux separately.
4. Core Concepts
4.1 configureStore
configureStore replaces the old createStore.
It automatically:
- Combines reducers
- Adds Redux DevTools
- Sets up middleware (including thunk)
- Enables good defaults
import { configureStore } from '@reduxjs/toolkit'
import counterReducer from './counterSlice'
export const store = configureStore({
reducer: {
counter: counterReducer
}
})
This is significantly cleaner than legacy Redux setup.
4.2 createSlice
createSlice is the most important feature of Redux Toolkit.
It:
- Generates action creators
- Generates action types
- Creates reducers
- Uses Immer internally for immutable updates
Example:
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
Notice something important:
We are "mutating" state directly, but Redux Toolkit uses Immer under the hood to keep state immutable.
This makes reducer logic far more readable.
4.3 createAsyncThunk
Handling async logic in classic Redux required middleware setup and manual action types (REQUEST, SUCCESS, FAILURE).
Redux Toolkit simplifies this using createAsyncThunk.
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'
export const fetchUsers = createAsyncThunk(
'users/fetchUsers',
async () => {
const response = await fetch('/api/users')
return response.json()
}
)
const usersSlice = createSlice({
name: 'users',
initialState: {
data: [],
loading: false,
error: null
},
extraReducers: (builder) => {
builder
.addCase(fetchUsers.pending, (state) => {
state.loading = true
})
.addCase(fetchUsers.fulfilled, (state, action) => {
state.loading = false
state.data = action.payload
})
.addCase(fetchUsers.rejected, (state, action) => {
state.loading = false
state.error = action.error.message
})
}
})
This automatically dispatches:
pendingfulfilledrejected
No manual action types needed.
4.4 createEntityAdapter
For managing normalized data (like lists of users, posts, products), createEntityAdapter helps structure state efficiently.
import { createEntityAdapter } from '@reduxjs/toolkit'
const usersAdapter = createEntityAdapter()
const initialState = usersAdapter.getInitialState()
const usersSlice = createSlice({
name: 'users',
initialState,
reducers: {
addUser: usersAdapter.addOne,
addUsers: usersAdapter.addMany
}
})
This automatically provides optimized selectors and CRUD helpers.
It is especially useful in large-scale applications.
5. Redux Toolkit with React
Wrap your app with Provider:
import { Provider } from 'react-redux'
import { store } from './store'
<Provider store={store}>
<App />
</Provider>
Use hooks inside components:
import { useSelector, useDispatch } from 'react-redux'
import { increment } from './counterSlice'
function Counter() {
const count = useSelector((state) => state.counter.value)
const dispatch = useDispatch()
return (
<div>
<p>{count}</p>
<button onClick={() => dispatch(increment())}>
Increment
</button>
</div>
)
}
Redux Toolkit works seamlessly with modern React patterns and functional components.
6. RTK Query (Built-in Data Fetching)
One of the most powerful additions in Redux Toolkit is RTK Query.
It eliminates the need for:
- Manual async thunks
- Separate loading states
- Caching logic
- Data refetch strategies
Example:
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
export const api = createApi({
reducerPath: 'api',
baseQuery: fetchBaseQuery({ baseUrl: '/api' }),
endpoints: (builder) => ({
getUsers: builder.query({
query: () => 'users'
})
})
})
export const { useGetUsersQuery } = api
Inside a component:
const { data, error, isLoading } = useGetUsersQuery()
RTK Query handles:
- Caching
- Deduplication
- Refetching
- Background updates
- Request lifecycle states
For modern applications, RTK Query is often preferred over manual createAsyncThunk.
7. Folder Structure Best Practices
A recommended feature-based structure:
src/
app/
store.js
features/
counter/
counterSlice.js
users/
usersSlice.js
usersAPI.js
Keep logic grouped by feature, not by type.
This improves scalability and maintainability.
8. Performance and Optimization
Redux Toolkit is already optimized, but best practices include:
- Use
createSelectorfor memoized selectors - Normalize large datasets
- Avoid unnecessary re-renders
- Keep state minimal and serializable
Redux Toolkit includes middleware that warns about:
- Non-serializable values
- Direct state mutation outside reducers
These safeguards help prevent common bugs.
9. Common Mistakes to Avoid
Overusing global state
Not everything belongs in Redux.Storing derived state
Compute it using selectors instead.Ignoring normalization for large datasets
UsecreateEntityAdapter.Writing manual thunks when RTK Query is better suited
Disabling middleware warnings without understanding them
10. When to Use Redux Toolkit
Redux Toolkit is ideal when:
- Your app has complex shared state
- Many components depend on the same data
- You need predictable state transitions
- You want robust debugging
- You require caching and advanced async logic
For small apps, React’s built-in state or context may be enough.
For medium to large applications, Redux Toolkit remains one of the most stable and scalable solutions available in 2026.
11. My Thoughts
Redux Toolkit transformed Redux from a boilerplate-heavy architecture into a streamlined, developer-friendly state management system.
Key takeaways:
- Use
configureStoreinstead ofcreateStore - Use
createSlicefor reducers and actions - Use
createAsyncThunkfor async logic - Prefer RTK Query for data fetching
- Follow feature-based structure
- Keep state minimal and normalized
If you are building production-grade React applications, Redux Toolkit provides reliability, scalability, and excellent developer experience—all backed by the official Redux team.
It is no longer just an alternative way to write Redux.
It is the standard way.
Top comments (0)