React.useReducer reference
In a nutshell
useReducer
is a React hook that lets you manage state in your function components. It has the following signature:
const [state, dispatch] = useReducer(reducer, initialState, init)
state
: the current state.dispatch
: function that dispatches an action.reducer
: a pure function that takes the previous state and an action, and returns the new state –(prevState, action) => newState
.initialState
: the initial state.
You can also use this signature if you want to lazily initialize the initial state:
const [state, dispatch] = useReducer(reducer, initialArg, init);
initialArg
will be passed as an argument to the init
function.
When to use useReducer
over useState
Both useReducer
and useState
let you manage component state so when should you prefer useReducer
?
State is complex
Sometimes, your state is complex and involves multiple sub-values. This usually means complex state updates that update multiple sub-values.
useReducer
lets you write your state updating logic as reducers – pure functions that take the previous state and an action, and return the next state. Because they’re pure functions, reducers are easy to test. When your state is complex, it’s useful and often essential to have good test coverage.
You need to trigger deep updates
If your component tree has several layers, and multiple components from different layers need to trigger state updates, you can combine useReducer
with the Context API to pass down dispatch
instead of callbacks.
For example, you can create a context and reducer:
const TodosContext = React.createContext(null)
function TodosApp() {
// Note: `dispatch` won't change between re-renders
const [todos, dispatch] = useReducer(todosReducer)
return (
<TodosContext.Provider value={dispatch}>
<DeepTree todos={todos} />
</TodosContext.Provider>
)
}
Then, from any component that’s wrapped with a Context.Provider
, you can access the dispatch
method to trigger updates.
function DeepChild(props) {
// If you want to perform an action, you can get dispatch from context.
const dispatch = useContext(TodosDispatch);
function handleClick() {
dispatch({ type: 'add', text: 'hello' });
}
return (
<button onClick={handleClick}>Add todo</button>
);
}
Shouldn’t you just use Redux?
Combining useReducer with React.Context
can go some way towards mimicking the behavior of Redux and unlocking its benefits (single source of truth = easier to debug app state, easier state persistence, etc).
It’s up to you whether you want to pull in Redux at this point. But Redux has its appeals:
- Redux DevTools
- Rich ecosystem of supporting libraries (e.g., redux-thunk,
redux-saga
) - Mature solutions to common problems (e.g., undo / redo, time travel, data fetching and caching)
Other notes
- React guarantees that the
dispatch
function identity is stable and won’t change between re-renders. So, you don’t have to includedispatch
in the dependencies list ofuseEffect
oruseCallback
. - If you return the same state from a reducer as the current state, React will bail out and not render child components or fire effects. React uses the
Object.is
to determine if the returned state is identical to the current state.
Sources
Thanks for your comment 🙏. Once it's approved, it will appear here.
Leave a comment