跪拜 Guibai
← All articles
Frontend

Custom Hooks Reuse Stateful Logic, Not State Itself

By 东风破_ ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

Custom Hooks are React's primary mechanism for composing stateful logic, but two misunderstandings trip up developers repeatedly: assuming shared logic means shared state, and treating StrictMode's double-fire as a framework defect rather than a cleanup audit. Getting the subscription-cleanup symmetry right prevents memory leaks and duplicate listeners that degrade performance in real applications.

Summary

A React component that tracks mouse coordinates with `useState` and `useEffect` mixes state management, browser event subscription, and UI rendering. Moving that logic into a custom `useMouse` Hook isolates the lifecycle-aware conversion of browser events into React state. The Hook returns only the coordinates; the component decides how to display them.

Several subtle pitfalls surface along the way. Checking `x && y` for a "mouse moved" condition breaks when coordinates are zero because JavaScript treats `0` as falsy; the correct check is `x !== null && y !== null`. Cleanup functions must reference the same function object passed to `addEventListener`, and React StrictMode's double-mount in development is a deliberate check for missing cleanup, not a bug.

Calling the same custom Hook from multiple components reuses the logic but creates independent state and effect instances. Sharing a single mouse position across components requires lifting the Hook call higher and distributing the result via props or Context.

Takeaways
Custom Hooks reuse stateful logic with lifecycle, not plain computation; they can call `useState` and `useEffect` internally.
Extracting `useMouse` moves browser event subscription and cleanup out of the component, leaving only rendering concerns.
Checking `x && y` for mouse movement is buggy because `0` is falsy in JavaScript; use `x !== null && y !== null` instead.
`useEffect` cleanup must reference the same function object passed to `addEventListener`, or the listener won't be removed.
React StrictMode intentionally mounts, unmounts, and remounts components in development to surface missing cleanup logic.
Calling the same custom Hook from multiple components creates independent state and effect instances; it does not share data.
To share one mouse position across many components, call the Hook once higher in the tree and pass the result via props or Context.
Extract a custom Hook when state and effects form a nameable, reusable unit, not just because code is long.
High-frequency events like `mousemove` don't automatically need throttling; optimize only when a measured performance problem exists.
Conclusions

The `x && y` falsy-coercion bug is a concrete example of why conditional rendering should express business semantics (`hasMoved`) rather than lean on JavaScript truthiness.

StrictMode's double-mount behavior is widely misunderstood as a bug; framing it as React verifying cleanup symmetry makes the design intention clearer.

The distinction between custom Hooks (reuse logic), Context (share data), and plain functions (reuse computation) gives developers a precise vocabulary for deciding where code belongs.

Concepts & terms
Custom Hook
A JavaScript function whose name starts with `use` that can call other React Hooks internally. It encapsulates stateful, lifecycle-aware logic for reuse across components without sharing state.
useEffect cleanup function
The function returned by a `useEffect` callback. React calls it before the component unmounts or before re-running the effect, allowing teardown of subscriptions, timers, or event listeners.
React StrictMode double-mount
In development, StrictMode intentionally mounts, unmounts, and remounts components once to detect side effects that lack proper cleanup. This does not happen in production builds.
Falsy value
In JavaScript, values like `0`, `''`, `null`, `undefined`, `NaN`, and `false` evaluate to false in boolean contexts. Relying on truthiness for conditional rendering can produce bugs when valid data like `0` is treated as absent.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗