react-native · memo
In one line: Hooks are slots on the fiber, matched by call order. Each render is a snapshot its closures capture. Effects sync with the outside world after commit; memo hooks keep identity. Most bugs: an old snapshot, or a dep with a new identity every render.
Download PDF Print view LaTeX source
State
useState(init):initused on mount only — pass a function for costly work. Setter is stable;Object.is(new, old)→ no render. Updates queue:set(c => c+1)twice = +2,set(c+1)twice = +1.useReducer: next = pure(state, action); stabledispatchto pass down. Pick it when fields change together. Never mutate:arr.push(x); set(arr)= same reference = no render.
Effects — timing, deps, cleanup
useEffect: after commit, normally after paint. Deps byObject.is; none = every render;[]= mount only.- Cleanup runs before the next run (old closure) and on unmount. Child effects run before the parent’s.
useLayoutEffect: before paint, sync — measure and fix without flicker (Fabric: layout readable). Blocks the frame.- Races: fast id changes → out-of-order responses. Cleanup cancels (
AbortController) or ignores (let stale = true). - StrictMode (dev): mount → cleanup → mount; double subscriptions = missing cleanup. Derived values belong in render, event work in handlers — not effects.
Refs
useRef = the same {current} box every render; writing it does not re-render (timers, latest value). On a host component: the native instance (focus(), measure()) — an IBOutlet. Not read during render.
Example — fetch without races
function useUser(id: string) {
const [user, setUser] = useState<User | null>(null);
useEffect(() => {
const ac = new AbortController();
fetch(`${API}/users/${id}`, { signal: ac.signal })
.then(r => r.json()).then(setUser)
.catch(e => { if (e.name !== 'AbortError') setErr(e); });
return () => ac.abort(); // before next id + on unmount
}, [id]); // omit id -> wrong user forever
return user;
}
React 19 (RN 0.78+)
use(promise | context) — suspends on a promise; the one hook allowed in conditions. useActionState, useOptimistic: async Actions with pending/optimistic state. ref is a plain prop (no forwardRef).
Memoisation — identity, not speed
useMemo(fn, deps)caches a value;useCallback(fn, deps)=useMemo(() => fn, deps);React.memo(C)skips a render on shallow-equal props.- Helps only when identity is consumed: prop of a
memo’d child (heavyFlatListrow), dep of another hook, contextvalue— or a truly expensive computation. Otherwise waste; one inline object/arrow/childrendefeatsmemo. - React Compiler memoises at build time; manual memo gets rare.
Context & external stores
useContext: every consumer re-renders whenvaluechanges (Object.is);memocannot block it. Memoisevalue, split contexts (state / dispatch), or use a store with selectors.useSyncExternalStore(subscribe, getSnapshot): tear-free reads of a non-React store (Zustand, Redux).getSnapshotmust return a cached value — a new object per call loops forever.
Rules of Hooks — why
- Top level only — no conditions, loops, or hooks after an early
return. No names or keys: order is identity. - Custom hook (
useX) shares logic, not state.
Interview traps
useCallbackwith nomemo’d consumer only adds cost.- Object/array deps built in render → effect every render (or a loop).
useEffect(async () => …)returns a promise, not a cleanup.- Silencing
exhaustive-depshides stale closures — restructure.
Remember
Order = identity. Render = snapshot. Effects sync; refs remember.
Likely questions
- Why no conditional hooks? — slots are matched by call order.
- useEffect vs useLayoutEffect? — after vs before paint.
- When does useCallback help? — a memo’d consumer.
- Context over-renders? — split, memoise value, selector store.