As you saw in an earlier article, the Redux store has a reducer function for updating its state. The reducer function receives the current state and an action, updates the state appropriately based on the action.type, and returns the next state.
When you finish this article, you should be able to:
switch statement within a reducer function to handle multiple action typesRecall the reducer from the Fruit Stand application:
const fruitReducer = (state = [], action) => {
switch (action.type) {
case 'ADD_FRUIT':
return [...state, action.fruit];
default:
return state;
}
};When the store initializes, it calls its reducer with an undefined state, allowing the reducer to dictate the store’s initial state via the state parameter’s default value.
The bulk of the reducer function then implements updates to the state. First, the reducer decides what logic to implement based on the action.type switch. Then, it creates and returns a new object representing the next state (after processing the action) if any of the information needs to be changed. The state is returned unchanged if no cases match the action.type, meaning that the reducer doesn’t care about that action (e.g. {type: 'NEW_TRANSFORMERS_SEQUEL'}).
In the above example, the reducer’s initial state is set to an empty array (i.e. []). The reducer returns a new array with action.fruit appended to the previous state if action.type is 'ADD_FRUIT'. Otherwise, it returns the state unchanged.
Additional case clauses can be added to update the reducer to handle the following action types:
'ADD_FRUITS' - Add an array of fruits to the inventory of fruits'SELL_FRUIT' - Remove the first instance of a fruit if available'SELL_OUT' - Someone bought the whole inventory of fruit! Return an empty arrayconst fruitReducer = (state = [], action) => {
switch (action.type) {
case 'ADD_FRUIT':
return [...state, action.fruit];
case 'ADD_FRUITS':
return [...state, ...action.fruits];
case 'SELL_FRUIT':
const index = state.indexOf(action.fruit);
if (index !== -1) {
// remove first instance of action.fruit
return [...state.slice(0, index), ...state.slice(index + 1)];
}
return state; // if action.fruit is not in state, return previous state
case 'SELL_OUT':
return [];
default:
return state;
}
};Array#slice worksIf you don’t regularly use the Array#slice method, the following expression might look odd at first glance:
The Array#slice method returns a new array containing a shallow copy of the array elements indicated by the start and end arguments. The start argument is the index of the first element to include and the end argument is the index of the element to include up to (but not including). If the end argument isn’t provided, all of the array elements up to the end of the array will be included. The original array will not be modified.
By combining two calls to the Array#slice method into a new array, a copy of an array can be created that omits an element at a specific index (index):
state.slice(0, index) - Returns a new array containing the elements starting from index 0 up to indexstate.slice(index + 1) - Returns a new array containing the elements starting from index + 1 (one past the index to omit the element at index) up through the last element in the arrayThen the spread syntax is used to spread the elements in the slices into a new array.
Here’s a complete example:
const fruits = ['apple', 'apple', 'orange', 'banana', 'watermelon'];
// The index of the 'orange' element is 2.
const index = fruits.indexOf('orange');
// `...fruits.slice(0, index)` returns the array ['apple', 'apple']
// `...fruits.slice(index + 1)` returns the array ['banana', 'watermelon']
// The spread syntax combines the two array slices into the array
// ['apple', 'apple', 'banana', 'watermelon']
const newFruits = [...fruits.slice(0, index), ...fruits.slice(index + 1)];This approach to removing an element from an array is just one way to complete the operation without modifying or mutating the original array.
Inside a Redux reducer, you must never mutate its arguments (i.e. state and action). Your reducer must return a new object if the state changes. Here’s why.
Here’s an example of a bad reducer which mutates the previous state.
const badReducer = (state = { count: 0 }, action) => {
switch (action.type) {
case 'INCREMENT_COUNTER':
state.count++;
return state;
default:
return state;
}
};And here’s an example of a good reducer which uses Object.assign to create a shallow duplicate of the previous state:
const goodReducer = (state = { count: 0 }, action) => {
switch (action.type) {
case 'INCREMENT_COUNTER':
const nextState = Object.assign({}, state);
nextState.count++;
return nextState;
default:
return state;
}
};In this article, you learned about reducers and how to use a switch statement within a reducer function to handle multiple action types. You also learned why it’s important for a reducer to avoid mutating the current state when creating the next state.
To learn more about reducers, see the official Redux documentation.