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
reducerinvoking to recalculate the new state and its argument will be passed to thereducerasaction
// 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></>)}