134 lines
3.1 KiB
Markdown
134 lines
3.1 KiB
Markdown
![]() |
---
|
||
|
tags:
|
||
|
- javascript
|
||
|
- react
|
||
|
---
|
||
|
|
||
|
# `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](React_useState.md) 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
|
||
|
|
||
|
```jsx
|
||
|
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 the `useState` hook. The functional role is the same,
|
||
|
it is just that the reducer offers more than one type of update.
|
||
|
|
||
|
### Example reducer
|
||
|
|
||
|
```js
|
||
|
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:
|
||
|
|
||
|
```js
|
||
|
{
|
||
|
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:
|
||
|
|
||
|
```js
|
||
|
dispatch({ type: "increase" });
|
||
|
```
|
||
|
|
||
|
To view the updated value:
|
||
|
|
||
|
```js
|
||
|
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:
|
||
|
|
||
|
```js
|
||
|
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:
|
||
|
|
||
|
```js
|
||
|
dispatch(
|
||
|
{
|
||
|
type: 'increase_by_payload'
|
||
|
payload: 3,
|
||
|
});
|
||
|
```
|
||
|
|
||
|
Then we would update our reducer to handle this case:
|
||
|
|
||
|
```js
|
||
|
function reducer(state, action) {
|
||
|
switch (action.type) {
|
||
|
...
|
||
|
case 'increase_by_payload':
|
||
|
return {...state, counter: state.counter + action.payload}
|
||
|
default:
|
||
|
throw new Error();
|
||
|
}
|
||
|
}
|
||
|
```
|