Custom hooks for state
Move repeated state logic into a use* hook with a stable callback.
From frontend-patterns, “Custom Hooks Patterns: State Management Hook”.
Three toggles, one hook
Flip each toggle and watch which lines own it.
AppSame behavior either way
Q3 revenue
$482,190
Invoices 214Paid 91%Overdue 7
sidebar: false · dark: false · details: falseCopied per component15 lines
const [sidebar, setSidebar] = useState(false)const toggleSidebar = useCallback(() => { setSidebar(v => !v)}, []) const [dark, setDark] = useState(false)const toggleDark = useCallback(() => { setDark(v => !v)}, []) const [details, setDetails] = useState(false)const toggleDetails = useCallback(() => { setDetails(v => !v)}, [])
With useToggle3 lines
const [sidebar, toggleSidebar] = useToggle()const [dark, toggleDark] = useToggle()const [details, toggleDetails] = useToggle()
export function useToggle(initialValue = false): [boolean, () => void] {
const [value, setValue] = useState(initialValue)
const toggle = useCallback(() => {
setValue(v => !v)
}, [])
return [value, toggle]
}
Toggle twice in one click
Set calls per click, then press Run.
setValue(!value)Stale
value: false
setValue(v => !v)Correct
value: false
const toggleBad = () => setValue(!value)
const toggle = useCallback(() => setValue(v => !v), [])
Stable callbacks skip renders
Type in the box, then press each child's button.
Inline arrow propNew fn each render
Parent renders1
value
false<MemoChild> renders1
onToggle={() => setOpen(v => !v)}toggle from useToggleSame fn
Parent renders1
value
false<MemoChild> renders1
onToggle={toggleOpen}const MemoChild = React.memo(function Child({ onToggle }: { onToggle: () => void }) {
return <button onClick={onToggle}>Toggle</button>
})
function Parent() {
const [query, setQuery] = useState('')
const [isOpen, toggleOpen] = useToggle()
return (
<>
<input value={query} onChange={e => setQuery(e.target.value)} />
<MemoChild onToggle={toggleOpen} />
</>
)
}
Why it matters, pros and cons
Why it matters
Copied logic drifts and goes stale. A hook fixes it once, with a stable callback.
Pros
- One implementation, one test, many callers.
- Stable identity keeps
React.memoeffective. - Callers name the tuple:
[isOpen, toggleOpen].
Cons
- A hook used once is just indirection.
useCallbackis noise unless identity is compared.- Hooks share logic, not state.