React Forum
Blog & Guide Responses
Nov 19, 2022 · 04:00 AM

Reusing logic with custom hooks - discussion (2022-11-19)

Started by Jules Kwon
externalreact.devhookssurfacescontractsabstractions

After reading this again, I’m realizing most of our hook problems aren’t “hooks are bad”, it’s that hook surfaces are unstable and hide important work (effects, fetches, identity churn).

Read the article

What rules do you use to keep custom hooks small and owned (inputs, outputs, stability)? Do you treat hook output shapes as contracts (and render a small debug marker when it matters), or is that overkill? How do you stop hooks from turning into mini-frameworks as teams pile on features?

Comments (10)

Back to latest
Maya Henson
Nov 19, 2022 · 04:06 AM

If a hook returns 12 things, it’s not reuse, it’s a module with a use prefix.

Dmitri Kline
Nov 19, 2022 · 04:14 AM

Treat hook surfaces like APIs: narrow surface, stable identities, and no hidden fetches.

When something is part of the render contract, make it route-visible instead of burying it in a hook.

Related: API Reference and Performance and Rendering Best Practices.

Salma Qureshi
Nov 19, 2022 · 04:22 AM

Stability trick: return memoized surfaces so consumers don’t rerender by accident:

tsx
export function useFiltersSurface() {
  const [q, setQ] = useState('');
  const actions = useMemo(() => ({ setQ }), [setQ]);
  const state = useMemo(() => ({ q }), [q]);
  return useMemo(() => ({ state, actions }), [state, actions]);
}
Keira Santos
Nov 19, 2022 · 04:31 AM

Counterpoint: I’d rather keep logic inline longer and only extract once the surface stabilizes.

Hooks are “cheap” to create, which is why they get abused.

Jules Kwon
Nov 19, 2022 · 04:36 AM

That’s reasonable. Our failure mode is the opposite: people keep bolting features onto the first hook that looks close enough.

So the hook becomes the dumping ground and nobody can reason about the contract.

Keira Santos
Nov 19, 2022 · 04:39 AM

Yep. Extracting later doesn’t help if you never stop adding responsibilities.

Camille Duarte
Nov 19, 2022 · 04:47 AM

We banned hooks that perform network calls and return { data, loading, error } as a pattern.

If data is a contract, it belongs at the route boundary with explicit cache posture.

Rowan Pierce
Nov 19, 2022 · 04:56 AM

The “render a small debug marker” suggestion sounds silly until you’ve had to debug a hook from a screenshot.

We render data-hook-surface='filters:v1' in dev builds and it’s saved time.

Amina Farouk
Nov 19, 2022 · 05:07 AM

Hidden effects inside hooks caused our nastiest timing bugs.

Once we required “effect markers” for hook-owned effects, the quality jumped because reviewers could see what the hook actually does.

Priya Liao
Nov 19, 2022 · 05:21 AM

Naming is surprisingly important. A vague hook name correlates strongly with a vague hook contract.

If you can’t name what it owns, it probably owns too much.

Inez Park
Nov 19, 2022 · 05:32 AM

A hook you can’t test without mocking half the app is a hook that’s doing too much.

Benji Rios
Nov 19, 2022 · 05:44 AM

Guardrail we used: hooks can’t write to global-ish state without an owner + reason string.

It felt annoying at first, then it prevented a bunch of accidental coupling.