useCounter IILoading saved progress…

useCounter II

You already built useCounter — a hook that returns a count plus increment, decrement, and reset. It works, but it rebuilds those three helper functions from scratch on every render, so each render hands the component a new function with the same behavior but a different identity. This version fixes that: the helpers must keep the same identity (===) across renders. Stable identities matter because a child wrapped in React.memo re-renders whenever a prop reference changes — passing it a fresh onClick every render quietly defeats the memoization.

Signature

function useCounterIi(initialValue?: number): {
  count: number;
  increment: () => void;
  decrement: () => void;
  reset: () => void;
};

The API is identical to useCounter. initialValue defaults to 0; reset restores that original value, not 0. The new requirement is purely about identity: across any number of renders, the increment, decrement, and reset you return must be the very same function objects.

Examples

function Stepper() {
  const { count, increment, decrement, reset } = useCounterIi(10);
  // Passing increment to a memoized child is safe: its identity never changes,
  // so the child does not re-render just because the parent did.
  return <Controls value={count} onUp={increment} onDown={decrement} onReset={reset} />;
}
// After a re-render, each helper is the SAME function object as before.
const first = result.current.increment;
rerender();
result.current.increment === first; // true

Notes

  • Identity is the whole point. Behavior is unchanged from useCounter; what is graded here is that the returned helpers are stable across renders.
  • increment and decrement still use functional updaters. setCount((c) => c + 1) reads the latest pending value, so an empty dependency array is correct and they never need to be recreated.
  • reset is the subtle one. Restoring the initial value while keeping a stable identity means you must not depend on initialValue in a way that recreates the function when it changes.
  • The count may go negative. decrement has no lower bound; clamping is out of scope.
Loading editor…