Build1 distinct publisher2 min readUpdated
A dev.to post makes the mechanical case: a hook runs inside the caller's render cycle, so moving state into one relocates the re-render instead of containing it.
The Engineer · Build desk
Compiled by The EngineerSomething wrong?How this is made
The accounting only looks good on one side. Extraction lifts about 50 lines of state and fetch logic out of the parent and leaves roughly 10 lines of JSX [9], an 80 percent cut in the parent's visible size [10]. Ownership does not travel with the lines. A custom hook is a function invoked during the caller's render, so state declared inside it belongs to the render cycle of whatever component called it [2]. The diff shows the saving. It does not show the subscription.
That is why the three remedies in the post are not interchangeable. Moving the input into its own component actually removes work: typing reaches `SearchInput` and stops, and `Dashboard` and `HeavyDataTable` do not re-run [5]. Splitting a God hook into `useUserProfile` and `useNotifications` [7] pays only when different components consume the halves. By the mechanism the post itself describes, both halves still execute in the render pass of any single component that calls both [2], so a split inside one consumer changes nothing about how often that consumer re-renders. Memoizing the hook's return value is narrower again: stable objects keep `React.memo` working for children [6], which protects those children from a re-render the host is performing anyway. One remedy deletes the render. Two reduce how far it travels. They are offered as a list of peers.
The uncomfortable case is the one the example is built from. A `useDashboardFilters` hook holding search query, date range and sorting sits at the root of the dashboard shell, and every keystroke re-runs the shell along with its data tables, sidebar and graphs [3]. A hook of that shape normally exists because something downstream reads the filters, and at that point the leaf-state fix is not available: the component that sets the value and the components that read it are in different subtrees, and their nearest common ancestor is the shell the advice is trying to spare. Keeping state near the leaves [8] is a placement rule, not a plan for state that two subtrees genuinely share.
The narrower version of the post's test survives that limitation. Before extraction, the useful question is not whether the logic is duplicated but which component ends up subscribed to the state, and whether that component needs to re-render when the state changes [11]. The argument here is mechanical rather than measured, with no profiler output or frame timings attached [12], so it cannot size the cost in any particular tree. It does say where the cost is: in the components sitting between the hook call and the leaf that triggers it.
Ranked by verification strength, evidence, and original report placement.
Custom hooks are excellent for sharing logic but do not isolate state; moving state into a custom hook does not hide the performance impact, it moves the source of the re-renders.
Custom hooks are just functions: when a component calls a hook, the hook's code runs as part of that component's render cycle, so any useState or useReducer update inside the hook triggers a re-render of the consuming component.
A useDashboardFilters() hook managing search queries, date ranges and sorting, imported at the root of a dashboard shell, means every keystroke in the search input updates hook state and forces the entire shell, including heavy data tables, sidebar navigation and complex graph components, to re-render.
In the post's example, a useSearch hook returning query and setQuery is called in Dashboard, so HeavyDataTable re-renders on every keystroke even though it does not care about intermediate keystrokes.
The post's primary fix is to keep state local: move the search input into its own SearchInput component with its own useState, after which typing re-renders only SearchInput while Dashboard and HeavyDataTable remain untouched.
The post's second strategy: if a hook returns objects or functions, keep them stable with useMemo, because returning a new object literal on every render breaks React.memo for any child component consuming that hook.
Evidence-backed comparisons of source perspectives and observed adoption signals. Read the methodology
Which Builder, Operator, and Investor concerns the observed source mix emphasized—not a truth score.
Evidence, demonstrated adoption, hype gap, incentives, and confidence are assessed independently, each on its own current evidence. How these are measured.
Mechanism explained, magnitude unmeasured
The core mechanism — a hook's code running inside the consumer's render cycle, so hook state updates re-render that consumer — is stated precisely and illustrated with before/after code that a reader can verify directly. But every performance consequence (sluggish input, dropped frame rates, 'massive bottleneck') is asserted; the only numbers in the piece are illustrative line counts, and there is a single source with no independent corroboration.
No adoption signal in supplied sources
The source is a technique argument with no release, deployment, usage disclosure, benchmark or any other observation about who follows this pattern or at what scale, so adoption cannot be measured without inventing facts.
Sound mechanism, inflated framing
The mechanical claims are accurate and modest, but the packaging overshoots them: a headline asking whether custom hooks are 'ruining' app performance, language about a 'silent performance killer' and dropped frame rates, and a cleanliness figure of 50 lines to 10, all with zero measurement of the runtime cost. The overstatement is in magnitude and urgency rather than in the underlying technical claim, so the gap is moderate rather than severe.
No incentive facts disclosed
The supplied material contains no vendor relationship, sponsorship, product being sold, funding or competing-interest disclosure for the author or publisher, so any incentive reading would be inferred rather than sourced.
Confident on mechanism, not on impact
Confidence is high that the story faithfully represents what the post argues and that its render-cycle reasoning is correct as stated, and low that the claimed user-visible performance impact holds at the asserted magnitude, since one publisher supplies the whole cluster, adoption is unobserved and no measurement is offered.
Follow any of these and your For You feed starts watching them — no settings page required.
build
The Escape-key listener you cannot fix with a dependency array1 distinct publisher
build
Codex learns to click: the coding agent stops typing patches and starts operating the machine1 distinct publisher
build
Five frameworks, one store, and a benchmark that measures when the button works1 distinct publisher
build
The 35% Lift Was The Tide: What A Bundled Commit Hid About Which Fix Worked1 distinct publisher
Distinct publishers with included, body-backed reporting in this cluster.
dev.to
1 article · August 21, 2026