# useCallback

## 1. The Basic API

`useCallback(fn, deps)` is `useMemo` specialized for one specific value: a function. It caches a *function reference* across re-renders, returning the exact same function identity as long as the dependency array hasn't changed:

```tsx
import { useCallback, useState } from "react";

function SearchBox({ onSearch }: { onSearch: (query: string) => void }) {
  const [query, setQuery] = useState("");

  // Without useCallback, handleSubmit is a BRAND NEW function every render —
  // a different reference every time, even though its behavior never changes
  const handleSubmit = useCallback(() => {
    onSearch(query);
  }, [query, onSearch]);

  return (
    <div>
      <input value={query} onChange={(e) => setQuery(e.target.value)} />
      <SubmitButton onClick={handleSubmit} />
    </div>
  );
}
```

`useCallback(fn, deps)` is exactly equivalent to `useMemo(() => fn, deps)` — same caching mechanism, same `Object.is`-per-element dependency comparison, just specialized so you write `fn` directly instead of a factory that returns `fn`.

## 2. Why Function Identity Matters at All

In plain JavaScript, function identity rarely matters. In React, it matters specifically because of two consumers that check reference equality:

```tsx
// 1. A dependency array of another hook
useEffect(() => {
  subscribe(handleMessage);
  return () => unsubscribe(handleMessage);
}, [handleMessage]); // if handleMessage is a new reference every render,
                       // this effect tears down and resubscribes every render

// 2. A memoized child component
const MemoizedChild = memo(function Child({ onClick }: { onClick: () => void }) {
  /* ... */
});
// <MemoizedChild onClick={handleClick} /> — if handleClick is a new
// reference every render, React.memo's shallow prop comparison always
// finds a "changed" prop, and the memoization is defeated
```

Without `useCallback`, a function declared inside a component body is a new object on every single render — closures are just objects under the hood, and every call to the enclosing function creates fresh ones. `useCallback` is the tool for handing a *stable* reference to consumers that specifically check `===` (or `Object.is`) identity.

## 3. useCallback Does Not Make the Function "Better" — Only Stable

A common misconception is that `useCallback` avoids "recreating" the function's logic, as if the function body were somehow expensive to construct. Creating a function object is essentially free in JavaScript — the actual cost `useCallback` addresses is entirely downstream, in whatever consumes the *reference*:

```tsx
// This function is trivial to create — useCallback here buys nothing on its
// own; it only matters if `increment` is passed to a memoized child or used
// as another hook's dependency
const increment = useCallback(() => setCount((c) => c + 1), []);

// If `increment` is never passed anywhere that checks reference equality,
// this is identical in behavior (and slightly worse in overhead) to:
const increment = () => setCount((c) => c + 1);
```

Reach for `useCallback` specifically because a function is: (a) a dependency of another hook, or (b) a prop passed to a `React.memo`-wrapped component — not reflexively on every handler.

## 4. Stale Closures Inside a Memoized Callback

`useCallback` does not change how closures work — it only decides *whether a new closure is created*. If the dependency array under-lists what the callback closes over, the *cached* function keeps closing over old values, same as any stale closure (see `languages/javascript`'s closures material for the underlying mechanics):

```tsx
function Counter() {
  const [count, setCount] = useState(0);

  // BUG: deps is [] but the callback reads `count` — the FIRST render's
  // count (0) is trapped in this closure forever; later renders' `count`
  // values are ignored because the cached function is never recreated
  const logCount = useCallback(() => {
    console.log(count); // always logs 0, no matter how many times count changes
  }, []); // missing `count`

  // FIX: list count as a dependency so the cached function is recreated
  // (and its closure refreshed) whenever count changes
  const logCountFixed = useCallback(() => {
    console.log(count);
  }, [count]);
}
```

The functional-updater form (`setCount(prev => ...)`, covered in the useState topic) often sidesteps this entirely by not needing to read the outer `count` at all — the safest fix for a callback that only needs to update state, not read it.

## 5. useCallback vs Inlining

Just like `useMemo`, `useCallback` should be applied where it solves a specific reference-identity problem, not by default on every function in a component:

```tsx
function Panel() {
  // No useCallback needed: this handler isn't passed to a memoized child
  // and isn't any other hook's dependency — a plain inline function is fine
  return <button onClick={() => console.log("clicked")}>Click</button>;
}
```

Wrapping every handler in `useCallback` "just in case" adds bookkeeping overhead (the dependency comparison itself) without a matching benefit, and it also invites Pitfall #1 below (missing dependencies) for no gain. See the Performance optimization topic for measuring whether memoization is worth it in a given case.

![useCallback(fn, deps) is useMemo(() => fn, deps) — the same hook node, storing the last function reference and dependency array](diagrams/01-usecallback-vs-usememo.svg)

## 6. Under the Hood

React does not attach state to the component *function* — a function component is just a function, called fresh every render with no persistent memory of its own. Hook data lives on the **fiber**, React's internal object representing one component instance, specifically in a **singly linked list of hook objects** hanging off the fiber's `memoizedState` pointer — one node per hook call, walked in the exact order hooks were called, every render. That fixed call-order requirement is why hooks (including `useCallback`) can never be called conditionally.

`useCallback`'s hook node stores the same two things a `useMemo` node stores: a cached value (here, a function reference) and the dependency array from the render that produced it. In fact, inside React's own source, `useCallback(fn, deps)` is implemented as a thin wrapper that calls the same internal mechanism as `useMemo(() => fn, deps)` — there is no separate "callback caching" machinery. On each render, React compares the new dependency array against the stored one element-by-element with `Object.is`; if everything matches, the node returns the *previously stored function reference* without evaluating `fn` again (there's nothing to "evaluate" — `fn` itself, as written in your source, is a fresh closure every render regardless; what `useCallback` skips is *returning* that fresh one and instead hands back the old one). If any dependency changed, React stores and returns this render's `fn`, which becomes the new cached reference going forward.

This clarifies exactly what's "created" and what's "reused": your component function body still constructs a brand-new closure object every single render — JavaScript does that unconditionally, `useCallback` cannot prevent it. What `useCallback` controls is which one of those closures actually gets *returned* to the rest of your component and handed to props/dependency arrays — the one from this render, or the previously cached one from a past render. A discarded closure that was never returned is simply garbage collected; it does no work and has no further effect.

Like `useMemo`, this caching is explicitly a performance optimization, not a correctness guarantee: React may discard a memoized callback in scenarios like reclaiming memory for offscreen trees, so component logic must remain correct even if a "new" function happened to be created and used every render.

## 7. Common Pitfalls

1. **Missing a closed-over dependency** — the cached function keeps its closure from whenever it was last recreated, silently reading stale values (a direct instance of stale-closure semantics — see `languages/javascript`'s closures material) until a listed dependency finally changes.
2. **Using `useCallback` without `React.memo` on the receiving component** — reference stability alone doesn't stop a child from re-rendering; `React.memo`'s shallow prop comparison is what actually benefits from the stable reference.
3. **Wrapping every handler "just in case"** — adds a dependency-comparison cost for no benefit when the function isn't a memoized child's prop or another hook's dependency.
4. **Passing an inline arrow function as one of the dependencies** (e.g. a new `() => {}` in the array) — the same inline-literal trap as `useMemo`; it's never `Object.is`-equal to the previous render's, so the cache never hits.
5. **Assuming `useCallback` avoids recreating the closure object** — it doesn't; the closure is created every render regardless. `useCallback` only decides which one gets returned.

## 8. Quick Reference

```
const fn = useCallback(() => doSomething(a, b), [a, b])
  → equivalent to useMemo(() => (() => doSomething(a, b)), [a, b])
  → returns the SAME function reference while a, b are unchanged

Use when: the function is (a) a React.memo child's prop, or
          (b) another hook's dependency array entry
Skip when: the function isn't consumed by anything that checks identity

Missing a dependency → stale closure, same rules as useEffect/useMemo
```
