Reusing logic with custom hooks - discussion (2023-02-02)
Custom hooks are one of those topics where everyone agrees in theory but disagrees in practice. The React docs framing (extract stateful logic, keep UI declarative) maps pretty well to this repo's contract mindset: hooks are a great place to define vocabulary (lanes, intent, posture) as long as they don't become invisible orchestration engines.
How do you decide whether a custom hook should return data, return actions, or return a whole contract bundle? Do you log hook behavior (lane transitions, decisions) or keep hooks silent to avoid noise? Where do you draw the line between a custom hook and a route-level document/vault?
Comments (14)
Back to latestMy rule: if the hook returns a *contract*, it should be stable and named (lane, posture, intent).
If it returns a grab bag, it's not a hook—it's a hidden module boundary.
We made hooks log only decisions, not data. That keeps it readable and useful. Example (the point is the shape):
txt
[tips] hook=useSearchLane lane=pending reason=query:change qLen=3
[tips] hook=useSearchLane lane=ok reason=fetch:resolve resultCount=27
If the hook can't explain its own transitions, it's usually doing too much orchestration and should be split.
Counterpoint: hook logs can be a trap. People treat them as truth and stop thinking about UI evidence.
I prefer hooks that *cause* evidence to exist (return an evidence string, or write to a doc) rather than hooks that spam the console.
Agree. If users and support can't see it, the log is only half the story.
We treat logs as dev-only; evidence is the product surface.
Line between hook and vault: hooks can *read* and *derive*; vaults store identity and persisted contracts.
If a hook starts owning identity (selectedId, activeRouteKey), it's probably a doc/vault responsibility.
Long-form take: hooks are most valuable when they define a vocabulary that multiple components can share without agreeing on component structure.
If the hook is basically a mini-component (it expects a certain render tree), it becomes brittle and people stop reusing it.
A practical boundary: hooks return *lanes and actions*, not JSX and not side effects.
If the hook performs navigation, measurement, or subscription, we make it an explicit named orchestration surface and log it as such.
Counterpoint: I've seen hooks become the place teams hide bad design.
Everything becomes useWhatever() and nobody knows where state lives anymore. Then debugging is impossible.
Yes. If state location isn't explicit, you end up with accidental global state through hooks.
The contract should say where truth lives (route doc vs vault vs local draft).
We document hook responsibilities by having them return a contractName string in dev.
Sounds silly, but it makes it harder to write a hook that does five different jobs.
Docs tie-in: the components & composition section basically implies this: keep hooks small and composable, not monolithic.
Related: Components and Composition and Patterns Library.
The best hooks we've shipped are basically adapters: they translate raw data into a derived view model.
They rarely "do" anything. They explain, and they make components boring.
Long-form counterpoint: sometimes teams over-index on reuse and end up with generic hooks that fit nobody.
I prefer hooks that are opinionated and local to a domain. If a hook is truly reusable, it will naturally spread.
One way to keep hooks honest is to tie them to a testable evidence key.
If useSearchLane produces data-search-lane, you can assert the contract from UI tests without peeking inside the hook.
We also added a small "hook decision log" panel in dev that prints only the last 10 decisions.
It kept logs readable without losing the story.
The article is good because it nudges you to think of hooks as composition, not inheritance.
That framing helps avoid hooks that try to control the whole app.