React Forum
Blog & Guide Responses
Aug 13, 2022 · 04:00 AM

Reusing logic with custom hooks - discussion (2022-08-13)

Started by Harper Iqbal
externalreact.devhookscustom hooksabstractionssurfaces

We have a growing pile of custom hooks and the quality variance is getting painful: some are small and clarifying, others are basically mini-frameworks.

Read the article

What criteria do you use before extracting a hook (API surface, stability, testing story)? How do you keep hooks from hiding effects, network calls, or identity churn that ripples through the tree? Do you treat hook output as a contract surface (and render some observable signal when it matters), or is that too much?

Comments (10)

Back to latest
Maya Henson
Aug 13, 2022 · 04:06 AM

If a hook returns 8 things, it's usually a module pretending to be a hook.

Dmitri Kline
Aug 13, 2022 · 04:14 AM

We got hooks under control once we treated them like APIs: small surface, stable behavior, and observable when they affect the contract.

Related: API Reference and Performance and Rendering Best Practices.

Salma Qureshi
Aug 13, 2022 · 04:22 AM

A pattern that avoids identity churn: return a stable surface and make actions stable on purpose.

tsx
type Surface<TState, TActions> = { state: TState; actions: TActions; debug: { version: string } };

export function useFiltersSurface() {
  const [q, setQ] = useState('');
  const setQuery = useCallback((next: string) => setQ(next), []);
  const actions = useMemo(() => ({ setQuery }), [setQuery]);
  const state = useMemo(() => ({ q }), [q]);
  return { state, actions, debug: { version: 'v1' } } as const;
}

If you need it observable, render debug.version in the shell or in a dev-only panel. The important part is your surface is stable.

Keira Santos
Aug 13, 2022 · 04:31 AM

Counterpoint: I think teams extract hooks too early.

If you can't name what the hook owns (posture, selection, fetch lane), keep it inline until the surface stabilizes.

Harper Iqbal
Aug 13, 2022 · 04:36 AM

Fair. Our problem is more the opposite: people keep adding features to existing hooks because it's convenient.

Then the hook becomes a dumping ground and nobody can reason about it.

Keira Santos
Aug 13, 2022 · 04:39 AM

Yep, that's the hook-as-framework failure mode. Better to split by surface ownership.

Theo Bennett
Aug 13, 2022 · 04:44 AM

The hooks I ban are the ones that call fetch internally and return data/loading/error with no visibility. If fetch is part of the render contract, I'd rather see it at the route boundary with an explicit cache lane.

ts
function useStableHookSurface() {
  const [q, setQ] = useState('');
  const actions = useMemo(() => ({ setQ }), [setQ]);
  const state = useMemo(() => ({ q }), [q]);
  return useMemo(() => ({ state, actions }), [state, actions]);
}
Hana Saito
Aug 13, 2022 · 04:52 AM

We treat the hook's returned shape as a surface contract.

If the shape changes, we call it a contractVersion bump and we update any UI signals/tests that depend on it.

Amina Farouk
Aug 13, 2022 · 05:03 AM

The worst bugs we had were timing bugs hidden inside hooks.

Once we required effects inside hooks to have boring cleanup and a visible signal when they change behavior, the quality jumped.

Jules Kwon
Aug 13, 2022 · 05:15 AM

Naming is 80% of it. If the hook name is vague, the hook surface is usually vague.

Inez Park
Aug 13, 2022 · 05:27 AM

If you can't debug the hook from a screenshot + a couple rendered signals, you'll debug it by reading code every time. That doesn't scale.

Benji Rios
Aug 13, 2022 · 05:41 AM

One practical rule: hooks can't write to global-ish state without an owner and a reason string.

Otherwise you get accidental coupling and nobody knows why the tree is rerendering.