React Forum
Blog & Guide Responses
Jan 08, 2023 · 04:00 AM

Reusing logic with custom hooks - discussion (2023-01-08)

Started by Mina Calder
externalreact.devcustom hooksreusecompositionrender contract

This article is often cited as "the right way" to share behavior without turning every feature into a mega component. I'm curious how people keep custom hooks honest as a *render contract* (what they read/write, what they assume) instead of an invisible grab bag of side effects.

Read the article

Do you have guidelines for what a custom hook is allowed to do (reads only, writes allowed, effects allowed)? When do you stop extracting hooks and just move the logic into a shared component boundary? How do you test hooks that encode motion (timers, subscriptions, route interaction) without snapshotting timing?

Comments (14)

Back to latest
Jules Kwon
Jan 12, 2023 · 04:18 PM

Our rule: a custom hook must declare its surface (inputs/outputs) like an API, not "just call it".

Keira Santos
Jan 19, 2023 · 09:41 AM

I like hooks that are obviously "read-only" (derive + format) vs hooks that write state.

The trouble is when the hook writes and the write is not visible anywhere else, so it becomes a second state machine.

Marisol Vega
Jan 27, 2023 · 04:02 AM

We treat a hook that contains an effect as a "boundary" and force it to render evidence in the consuming component.

If it subscribes, we require an observable status string like subscribed|paused|error so it isn't purely timing-based.

Dmitri Kline
Feb 06, 2023 · 09:10 PM

A pattern that's worked: hooks can *compute*, but writes are explicit and passed in as callbacks.

ts
function useFiltered(items: any[], query: string) {
  const q = query.trim().toLowerCase();
  return q ? items.filter((x) => String(x.title ?? '').toLowerCase().includes(q)) : items;
}

Then the component decides whether the derived list is stored, memoized, or recomputed.

Rowan Pierce
Feb 17, 2023 · 12:32 PM

Counterpoint: sometimes the hook *should* be allowed to write state, because that's the point of reuse.

But we keep the write surface tiny: a single setX with a named intent object, not random internal setState calls.

Mina Calder
Feb 21, 2023 · 06:25 PM

That makes sense. I think my worry is untracked effects and hidden writes, not writes in general.

The intent object idea is nice because it forces the hook to admit what kind of state transition it is making.

Salma Qureshi
Mar 03, 2023 · 07:56 AM

Our extraction heuristic: if the behavior needs to render UI evidence (spinners, policy flags, route posture), keep it as a component boundary.

If it's pure calculation or event wiring, extract to a hook.

Amina Farouk
Mar 15, 2023 · 08:40 PM

Testing-wise we stopped trying to unit test "timing" hooks and instead tested the visible surface: status flags and derived outputs.

If the hook's behavior isn't expressible as visible evidence, it's too magical to be reusable anyway.

Owen Price
Mar 30, 2023 · 10:22 AM

Worth connecting this to the forum's docs posture: store derived state when siblings need consistency.

Hook extraction sometimes accidentally duplicates derivations across consumers.

Related: State Management (Best Practices).

Jasper Nguyen
Apr 18, 2023 · 03:14 AM

I prefer the "shared component" option when I need lifecycle to be legible.

Hooks can hide lifetime because they piggyback on the caller, which is great until you want to reason about mount/unmount as a feature.

Noah Vance
May 06, 2023 · 10:55 PM

One pitfall: hooks that read route state implicitly (like pulling from a global route) make reuse misleading.

If a hook depends on route posture, I'd rather pass route keys in explicitly so you can see what it needs.

Inez Park
May 27, 2023 · 02:06 PM

We document hook surfaces in code review: "reads: A,B. writes: C. effects: subscription".

It's boring but it prevents hooks from turning into spooky action at a distance.

Camille Duarte
Jun 19, 2023 · 08:01 AM

Another angle: extraction is often premature. The first version should probably be inlined in a component so you learn what the surface actually is.

Once you know the surface, the hook is easy to keep honest.

Benji Rios
Aug 02, 2023 · 04:33 PM

I still see teams extract hooks to avoid passing props, then they re-invent context, then they regret it.

Sometimes deep props are the simpler contract, especially inside a single route shell.

Hana Saito
Sep 14, 2023 · 05:42 AM

If you're using hooks for shared motion, consider pairing them with a visible debug line in the UI (even behind a flag).

It makes the hook's lifecycle feel like part of the app, not an invisible mechanism.