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
dispatchfunction identity is stable and won’t change between re-renders. So, you don’t have to includedispatchin the dependencies list ofuseEffectoruseCallback. - 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.isto 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