useReducer
The useReducer
hook is best used in scenarios where you are manipulating state in a way that is too complex for the trivial React_useState use case. useState
is best employed when you are updating a single value or toggling a boolean. If you are updating the state of an object or more complex data structure, it is often more efficient to employ useReducer
.
This makes the code more manageable and also helps with separating state management from rendering.
Syntax
const [state, dispatch] = useReducer(reducer, initialState);
initialState
- The starting state, typically an object
reducer
- A pure function that accepts two parameters:
- The current state
- An action object
- The reducer function must update the current state (immutably) and return the new state
- We can think of the reducer as working in the same manner as
state
/setState
in theuseState
hook. The functional role is the same, it is just that the reducer offers more than one type of update.
- A pure function that accepts two parameters:
Example reducer
function reducer(state, action) {
let newState;
switch (action.type) {
case "increase":
newState = { counter: state.counter + 1 };
break;
case "descrease":
newState = { counter: state.counter - 1 };
break;
default:
throw new Error();
}
return newState;
}
In this example we are updating an object with the following shape:
{
counter: 0,
}
This would be the initialState
that we pass to the useReducer
hook along with a reference to reducer
above.
To update the state we would invoke the dispatch
function which applies one of the actions defined in the reducer. For example the following dispatch increments the counter by one:
dispatch({ type: "increase" });
To view the updated value:
console.log(state.counter);
Refining the syntax
Because React doesn’t mutate state, the reducer doesn’t directly modify the current state in the state
variable, it creates a new instance of the state object on each update.
In the reducer example above this is achieved by declaring a variable newState
that is updated by each action
type and then returned. There is a more elegant way of doing this using spread syntax:
function reducer(state, action) {
switch (action.type) {
case "increase":
return { ...state, counter: state.counter + 1 };
break;
case "decrease":
return { ...state, counter: state.counter - 1 };
break;
default:
throw new Error();
}
}
Including payloads
In the examples so far, we have updated the the state directly via the action type however it is also possible to pass data along with the action.type
as action.payload
.
For example:
dispatch(
{
type: 'increase_by_payload'
payload: 3,
});
Then we would update our reducer to handle this case:
function reducer(state, action) {
switch (action.type) {
...
case 'increase_by_payload':
return {...state, counter: state.counter + action.payload}
default:
throw new Error();
}
}