I have learned about redux toolkit for 2 months, and in createSlice
have reducers
and extrareducers
, I know they use to change state from dispatch but I don't know the difference, Where should we use them?
6 Answers
The reducers
property both creates an action creator function and responds to that action in the slice reducer. The extraReducers
allows you to respond to an action in your slice reducer but does not create an action creator function.
You will use reducers
most of the time.
You would use extraReducers
when you are dealing with an action that you have already defined somewhere else. The most common examples are responding to a createAsyncThunk
action and responding to an action from another slice.

- 38,446
- 6
- 64
- 102
extrareducers
is actually like reducers
with enhancements, but it's been built to handle more options, esecially other actions (like the ones generated in other slices or actions made by createAction
or createAsyncThunk
). In one word
Everything you can dispatch in redux can be added to it.
The extrareducers
property in createSlice
can be used as a function or as an object.
The function form has an input argument named builder
.
Example 1: The builder.addCase
function adds an action from another slice (with Typescript type safety).
const {actions,reducer} = createSlice({
name:'users',
reducers:{
......
},
initialState:.... ,
extraReducers:(builder) => {
builder.addCase(authActions.logout, state => {
state.isLoggedIn = false;
});
}
});
The authActions.logout
here is another action from another slice.
If you create an action using createAsyncThunk
function (which is imported from "@reduxjs/toolkit") You can handle loading, success & failure states.
Example 2: Handling loading state from an async Thunk:
const fetchUserById = createAsyncThunk(
'users/fetchByIdStatus',
async (userId, thunkAPI) => {
const response = await userAPI.fetchById(userId)
return response.data
}
)
const {actions,reducer} = createSlice({
name:'users',
reducers:{
......
},
initialState:.... ,
extraReducers: (builder) => {
......
......
builder.addCase(fetchUserById.pending, (state, action) => {
state.loading=true;
state.whoseDataIsLoading = action.payload;
})
}
});
- fetchUserById.pending (handles loading state of the asyncThunk)
- fetchUserById.rejected (handles failed state)
- fetchUserById.fulfilled (handle success state)
The builder also accepts addDefaultCase
and addMatcher
in which the addDefaultCase
acts as the default case in switch statement used by conventional reducers (reducers in the redux without toolkit) and,
addMatcher is used with a type predicate function a function used to infer that some input has some particular type (type of action in here) to ensure the action has a particular type, then the state changes.
Example 3: The extrareducers
as an object:
const {actions,reducer} = createSlice({
name:'users',
reducers:{
......
},
initialState:.... ,
extraReducers: {
['incrementBy']: (state, action) => {
state.someValue += action.payload
}
}
});
References:

- 2,809
- 1
- 21
- 26
let's say you have those two reducers:
const booksSlice = createSlice({
name: "books",
initialState: [],
reducers: {
addBook(state, action) {
// this state is not global state. it is only books slice
state.push(action.payload);
},
resetBooks(state, action) {
// immers assume whatever you return you want your state to be
return [];
},
},});
export const { addBook, resetBooks } = booksSlice.actions;
const authorsSlice = createSlice({
name: "authors",
initialState: [],
reducers: {
addAuthor(state, action) {
state.push(action.payload);
},
resetAuthors(state, action) {
return [];
},
},});
export const { addAuthor, resetAuthors } =authorsSlice.actions;
Both reducers have initial state is []. Let's say you display "books" and "authors" list in a page and you want to have a button to reset the both state.
<button onClick={() => handleResetLists()}>
Clear the page
</button>
inside handleResetLists
you can dispatch both actions
const handleReset = () => {
dispatch(resetBooks());
dispatch(resetAuthors());
};
But instead, I could tell authorsSlice
to watch for the additional action types with extraReducers
const authorsSlice = createSlice({
name: "authors",
initialState: [],
reducers: {
addAuthor(state, action) {
state.push(action.payload);
},
// builder tells this slice to watch for additional action types
extraReducers(builder) {
// second argument will update the state
builder.addCase(booksSlice.actions.reset, () => {
return [];
});
},
},
});
Now inside handleReset
function, I will dispatch an action to book reducer but author reducer will be watching for this action and it will also update
const handleReset = () => {
dispatch(resetBooks());
// I do not need to dispatch this
// dispatch(resetAuthors());
};
In the above implementation, authorsSlice
is dependent on booksSlice
because we use booksSlice.actions.reset
inside extraReducer
. Instead we create a separate action because what if we get rid of bookSlice
in the future
import {createAction} from "@reduxjs/toolkit"
// we are gonna dispatch this
export const resetAction=createAction("lists/reset")
inside both slicer add this extraReducer:
extraReducers(builder) {
builder.addCase(resetAction, (state, action) => {
state = [];
});
},
inside the page
import {resetAction} from "./store/actions"
const handleReset = () => {
dispatch(reset());
// I do not need to dispatch this
// dispatch(resetAction());
};
- Note that
extraReducer
is not part of the actions that generated by theslice.actions

- 35,338
- 10
- 157
- 202
If action is supposed to be handled by one reducer, use reducers
.
If action is supposed to be handled by multiple reducers, use extraReducers
.

- 7,944
- 5
- 56
- 56
Suppose, you have one TodoReducer.js file in that you have manageTodo reducer:
import {createSlice} from '@reduxjs/toolkit';
const initialState = {
todo: [],
};
export const manageToDo = createSlice({
name: 'Todo',
initialState: initialState,
reducers: {
add: (state, action) => {
console.log(action);
state.todo = [...state.todo, action.payload];
},
del: (state, action) => {
state.todo.splice(action.payload, 1);
},
edit: (state, action) => {
state.todo.splice(action.payload.index, 1, action.payload.item);
},
clearTodo: state => {
state.todo = [];
},
},
});
You want that,
On add todo there is increment in counter &
On delete todo there is decrement in counter
so you can utilise the extraReducer
Like This:
CounterReducer.js
import {createSlice} from '@reduxjs/toolkit';
import {manageToDo} from './TodoReducer';
const counterInitialState = {
counter: 0,
};
const manageCounter = createSlice({
name: 'Counter',
initialState: counterInitialState,
reducers: {
increment: (state, action) => {
console.log('testing increment', state.counter);
state.counter++;
},
decrement: (state, action) => {
state.counter--;
},
},
extraReducers: builder => {
builder.addCase(manageToDo.actions.clearTodo, () => {
return 0;
});
builder.addCase(manageToDo.actions.add, state => {
state.counter++;
});
builder.addCase(manageToDo.actions.del, state => {
state.counter--;
});
},
});
export const manageCounterReducer = manageCounter.reducer;
export const {increment, decrement} = manageCounter.actions;

- 1
- 2
ExtraReducer is reducer and its role is to be reducer. I learned and used it with createAsyncThunk(). Role of (extra)reducer is to be pure function with two parameters actual state and action. Reducer is one slice of reducers, but extraReducer are three slice of reducer, pending, fullfiled and reject. Every slice of (extra)reducer is action creator.
Every action creator are being dispatch to store.
Thunk is function in middleware pattern. Thunk is action creator which create three more action creator: pending, fullfiled and reject. These three action creators are three extraReducers what immutably update state three time.
More: https://www.youtube.com/watch?v=2M-HR2J88S4

- 544
- 6
- 13