React
useReducer
useReducer
is an alternative to useState, but usually used in more advanced use cases. useReducer
is actually inspired by Redux, so if you"re familiar with Redux, you already know how useReducer
works.
useReducer
is prefered to useState
when you have complex state logic that involves multiple sub-values or when the next state depends on the previous one. useReducer
also lets you optimize performance for components that trigger deep updates because you can pass dispatch down instead of callbacks.
useReducer
has 3 arguments:
- reducer: a function that takes in
state
,action
, does some calculations and returns thenewState
. - initialState: the initial value of the state.
- lazyInitFunction (Optional): if present, the initial state will be set to the returned of
lazyInitFunction(initialState)
calling.
useReducer
returns an array consisting of 2 things:
- state: the current value of the state
- dispatch: a function that will trigger a
reducer
invoking to recalculate the new state and its argument will be passed to thereducer
asaction
// Calling signature
const reducer = (state, action) => newState
const [state, dispatch] = useReducer(reducer, initialState, lazyInitFunction);
Basic Usage
Below is the counting example from useState section, rewritten to use a reducer
function Count() {const initialState = { count: 0 }function reducer(state, action) {switch (action.type) {case "increase":return { count: state.count + 1 }case "decrease":return { count: state.count - 1 }default:throw new Error()}}const [state, dispatch] = useReducer(reducer, initialState)return (<><div>Count: {state.count}</div><Button onClick={() => dispatch({ type: "decrease" })}>-</Button><Button onClick={() => dispatch({ type: "increase" })}>+</Button></>)}
Lazily Initialize State
If lazyInitFunction
presents, the initial state will be set to the returned of lazyInitFunction(initialState)
calling.
function Count() {const initialCount = 10function reducer(state, action) {switch (action.type) {case "increase":return { count: state.count + 1 }case "decrease":return { count: state.count - 1 }default:throw new Error()}}// Initial state will be { count: 10 }function lazyInitFunction(initialCount) {return { count: initialCount }}const [state, dispatch] = useReducer(reducer, initialCount, lazyInitFunction)return (<><div>Count: {state.count}</div><Button onClick={() => dispatch({ type: "decrease" })}>-</Button><Button onClick={() => dispatch({ type: "increase" })}>+</Button></>)}
Warning
As mentioned in the warning in the useState section, React uses shallow comparison when checking for state differences. If you return the same Object or Array value from a Reducer Hook as the current state, React will bail out without re-rendering the children or firing effects.
function Count() {const initialCount = { count: 10 }function reducer(state, action) {switch (action.type) {case "decrease":state.count = state.count - 1return statecase "increase":return { count: state.count + 1 }default:throw new Error()}}const [state, dispatch] = useReducer(reducer, initialCount)return (<><div>Count: {state.count}</div><Button onClick={() => dispatch({ type: "decrease" })}>- (not rerender)</Button><Button onClick={() => dispatch({ type: "increase" })}>+</Button></>)}