In a previous lesson, you learned about createSlice
. In this lesson, you will learn about extraReducers
, a property you can optionally pass to createSlice
that allows createSlice
to respond to action types it did not generate.
To refresh your memory, createSlice
accepts a single argument, options
, which is an object containing configuration parameters including a name, some initial state, and reducers. createSlice
then uses these configuration parameters to generate a slice of the store, including action creators and action types for updating the state contained in that slice. Consider the following example:
const usersSlice = createSlice({ name: 'users', initialState: { users: [] }, reducers: { addUser: (state, action) => { state.users.push(action.payload) } }, })
This call to createSlice
, generates a slice of the store that responds to the action creator usersSlice.actions.addUser
. But what if we’ve generated our action creators via calls to createAsyncThunk
? Consider fetchUserById
, the asynchronous action creator from earlier in this lesson:
const fetchUserById = createAsyncThunk( 'users/fetchUserById', // action type async (userId, thunkAPI) => { // payload creator const response = await fetchUser(userId) return response.data } )
This asynchronous action creator will generate three action types: 'users/fetchUserById/pending'
, 'users/fetchUserById/fulfilled'
, and 'users/fetchUserById/rejected'
. Currently, these action types have no effect on our users slice, which only responds to the users/addUser
action type generated by createSlice
.
How can we account for these promise lifecycle action types in our user slice? This is exactly the problem that extraReducers
, an optional property on the configuration object passed to createSlice
, was designed to solve. extraReducers
allows createSlice
to respond to action types generated elsewhere. To make the users slice respond to promise lifecycle action types, we pass them to createSlice
in the extraReducers
property.
Open usersSlice.js
in your code editor to see an example of the extraReducers
property in context.
Note that in addition to using the extraReducers
property, we also added some extra fields to our state object: a boolean, isLoading
, which will be true when a request is pending, and otherwise false, and a boolean hasError
, which we will set to true
if our request to fetch a user is rejected. These additions allow us to track promise lifecycle states so that we can create satisfying and informative user interfaces when the promise is either pending
or rejected
. When the promise is fulfilled
these are set to false
and the user data is added to the state.
Instructions
In allRecipesSlice.js
, we’ve used createAsyncThunk
to define loadRecipes
, an asynchronous action creator that fetches all our app’s recipes, and createSlice
to define a slice of recipes in our app’s store.
Add two booleans — isLoading
and hasError
— to the initialState
property passed to createSlice
. What should their initial values be?
Using the extraReducers
property, add reducers for each of the promise lifecycle action types generated by createAsyncThunk
.
What about the app’s behavior has changed? While the recipes are being fetched, the app displays a loading spinner. And if the recipes fail to fetch, the app displays an error message.
Why does the app behave differently when you pass extra Reducers to createSlice
? Adding the extra reducers to the recipes slice causes the store to update in response to each of the pending/fulfilled/rejected actions dispatched by loadRecipes
. These changes are reflected in the app’s UI.