Suggest an editImprove this articleRefine the answer for “Passing callback functions”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**Extra re-renders from callbacks happen when a child component is wrapped in `React.memo`, but the function-prop reference changes on every parent render.** The fix is a stable function identity plus careful memoization. **Key point:** without `React.memo` on the child, a stable callback will not help - the child re-renders with the parent anyway.Shown above the full answer for quick recall.Answer (EN)Image**Extra re-renders from callbacks happen when a child component is wrapped in** `React.memo`**, but the function-prop reference changes on every parent render.** The fix is a stable function identity plus careful memoization. --- ## What to do 1. **Make callbacks stable (**`useCallback`**)** ```javascript const handleSelect = useCallback((id: string) => { setSelected(id); // can go through a functional update }, []); // only the dependencies you actually need ``` > A stable reference -> fewer chances to "wake up" `React.memo` children. 2. **Memoize the child (**`React.memo`**)** ```javascript const Item = React.memo(function Item({ onSelect, item }) { return <button onClick={() => onSelect(item.id)}>{item.title}</button>; }); ``` > Without `React.memo` on the child, a stable callback will not help - it re-renders with the parent anyway. 3. **Avoid function "factories" in JSX** ```javascript // bad: a new function every time {items.map(i => <Item key={i.id} onSelect={() => onSelect(i.id)} item={i} />)} // good: one stable function + the data goes as a separate prop const onSelect = useCallback((id: string) => { /* ... */ }, []); {items.map(i => <Item key={i.id} onSelect={onSelect} item={i} />)} ``` 4. **Use functional updates so you do not have to pull state into dependencies** ```javascript // bad: pulls selected into deps -> breaks stability const onToggle = useCallback(() => setSelected(selected + 1), [selected]); // good: the reference is stable const onToggle = useCallback(() => setSelected(s => s + 1), []); ``` 5. **When you need the "latest" state but a stable reference - the useEvent pattern (through a ref)** ```javascript function useEvent<T extends (...a: any[]) => any>(fn: T): T { const ref = useRef(fn); useLayoutEffect(() => { ref.current = fn; }); return useCallback(((...args) => ref.current(...args)) as T, []); } // Usage: const onChange = useEvent((value: string) => { // reads the freshest state/props, but the callback's reference never changes }); ``` 6. **Do not pass new objects with callbacks every time** ```javascript // bad: options is new -> wakes up the child <Chart options={{ onHover }} /> // good: memoize the object prop const chartOptions = useMemo(() => ({ onHover }), [onHover]); <Chart options={chartOptions} /> ``` 7. **Sometimes it is simpler to change the props API** - Instead of `onSelect={() => onSelect(item.id)}`, pass `id` and create the handler itself inside `Item`. - Or pass a single `dispatch` (from `useReducer`) - **its reference is stable by contract**. 8. **Split components and lift memoization higher** - Move the "heavy" part into a `React.memo` component, so that even when the callback changes, only a small node re-renders. --- ## What not to do (common pitfalls) - **Put** `useCallback` **everywhere.** It also costs a bit of CPU. Use it where: 1. the child is memoized (`React.memo`/`memo`), **and** 2. the function actually goes to it as a prop. - **Throw everything into the dependency array.** Keep the deps array minimal and correct; for state, prefer functional updates more often. - **Use an array index as the** `key` in lists with callbacks - reordering will lose the memo benefit and the children's state.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.