useState
is typically simpler at first than useReducer
(you can even implement useState
using useReducer
), but there's one scenario where useReducer
is almost certainly better than useState
:
When one element of your state relies on the value of another element of your state in order to update:
useReducer
More information: KcD's blog
import React from 'react' import ReactDOM from 'react-dom' function undoReducer(state, action) { const {past, present, future} = state const {type, newPresent} = action switch (type) { case 'UNDO': { if (past.length === 0) return state const previous = past[past.length - 1] const newPast = past.slice(0, past.length - 1) return { past: newPast, present: previous, future: [present, ...future], } } case 'REDO': { if (future.length === 0) return state const next = future[0] const newFuture = future.slice(1) return { past: [...past, present], present: next, future: newFuture, } } case 'SET': { if (newPresent === present) { return state } return { past: [...past, present], present: newPresent, future: [], } } case 'RESET': { return { past: [], present: newPresent, future: [], } } default: { throw new Error(`Unhandled action type: ${type}`) } } } function useUndo(initialPresent) { const [state, dispatch] = React.useReducer(undoReducer, { past: [], present: initialPresent, future: [], }) const canUndo = state.past.length !== 0 const canRedo = state.future.length !== 0 const undo = React.useCallback(() => { dispatch({type: 'UNDO'}) }, []) const redo = React.useCallback(() => { dispatch({type: 'REDO'}) }, []) const set = React.useCallback(newPresent => { dispatch({type: 'SET', newPresent}) }, []) const reset = React.useCallback(newPresent => { dispatch({type: 'RESET', newPresent}) }, []) return [state, {set, reset, undo, redo, canUndo, canRedo}] } function App() { const [state, {set}] = useUndo('first') React.useEffect(() => { set('second') }, [set]) React.useEffect(() => { set('third') }, [set]) return <pre>{JSON.stringify(state, null, 2)}</pre> } ReactDOM.render(<App />, document.getElementById('root'))