Two React components each call the same custom hook, useCounter(). If one component increments its counter, does the other component's value change? Explain what a custom hook does and does not share.
answer
- logic is shared, instances are not
- each call site gets its own slots
- state attaches to the calling component
- module scope is the exception
- sharing needs one owner, not one hook
basics
~20 sNo. A custom hook shares logic, not state: every call site gets its own independent state, so the two counters are unrelated. Sharing one value requires lifting it to a common parent, putting it in context, or using an external store.
solid answer
~50 sThey stay independent. A custom hook is just a function, and the `useState` inside it registers state on whichever component is rendering at that moment — so two components calling `useCounter()` produce two separate state slots, and incrementing one does not re-render or change the other. Even calling `useCounter()` twice inside the *same* component gives you two unrelated counters. The one exception is anything the hook keeps outside React: a module-scope variable or a singleton it subscribes to is shared by every caller, because it lives in the module, not in a component. If I genuinely need one shared value, the hook is the wrong tool for sharing — I lift the state to the closest common parent, expose it through context, or subscribe every caller to one external store. The custom hook then becomes the ergonomic wrapper around whichever of those I chose.
go deeper
Memorise the headline: a custom hook shares the code, not the values. Two components calling it get two separate pieces of state, so incrementing one does nothing to the other.
Explain the mechanism — the useState inside the hook registers on whichever component is rendering, so state lives per component instance — and note that calling the same hook twice in one component also yields independent state.
Show you know the leaky case: module-scope variables and singletons inside a hook are shared but do not schedule renders, so callers drift out of sync; every caller must subscribe to a shared source rather than read it once.
Frame the choice of owner: which state deserves lifting, which belongs in a provider, and which justifies an external store — and set the convention that hooks wrapping shared state read one source instead of each creating their own.
## Why the answer is "no" A custom hook is an ordinary function. When `useCounter()` runs, it is running *inside* the render of some component, and the `useState` call it makes is recorded against that component. React keeps hook state in a list attached to the rendering component's internal node (its fiber), matched by call order. Two components rendering means two nodes, each with its own list — so `useCounter()` in `<Header/>` and `useCounter()` in `<Sidebar/>` are two entirely separate state slots. ```javascript function useCounter(initial = 0) { const [count, setCount] = useState(initial); const increment = useCallback(() => setCount((c) => c + 1), []); return [count, increment]; } ``` Nothing in that function body could plausibly connect two callers: `useState` returns whatever value is stored on the current component and a setter bound to that same slot. What the two components share is the *source code* — the wiring, the update rule, the effect cleanup logic. That is the whole point of the abstraction, and it is the sentence interviewers are listening for: **custom hooks share logic, not state.** ## The same is true within one component ```javascript const [red, incRed] = useCounter(0); const [blue, incBlue] = useCounter(0); ``` These are two independent counters. React does not deduplicate hook calls by identity, name, or arguments; it walks the hook calls in order and hands each one its own slot. This is exactly why the rules about calling hooks unconditionally exist — the slots are matched positionally, not by name. ## The exception: anything outside React State *inside* React is per-component. Anything the hook touches outside React is not: ```javascript let cache = null; // module scope — one for the whole app export function useConfig() { const [config, setConfig] = useState(cache); // every caller reads and writes the same `cache` } ``` A module-level variable, a singleton client, a `window` listener, a WebSocket — all of these are shared by every component that calls the hook, because they live in the module, not in a component instance. That mismatch is a real source of bugs: two callers appear to share data (they read the same cache) but do not share re-renders (each has its own React state), so one component updates and the other keeps showing a stale value until something else re-renders it. If a hook is going to expose a shared external source, every caller must subscribe to it so they all update together — that is precisely the problem `useSyncExternalStore` exists to solve. ## How to actually share state When two components must see the same value, you need a single owner: - **Lift it up.** Put the `useState` in the nearest common ancestor and pass the value and updater down. Simplest, and the right default when the components are close together. - **Context.** One provider owns the state; consumers read it. Good when the distance is large or the value is genuinely ambient; the trade-off is that consumers re-render when the provided value changes identity. - **An external store.** One object outside React holds the value, and every component subscribes to it. Appropriate when the state is app-wide, updated frequently, or must be read from outside the React tree. In all three cases you still write a custom hook — but its job changes. Instead of *owning* state with `useState`, it *reads* the single shared source: `useContext(CartContext)` or a store subscription, plus validation that a provider exists. That hook shares state with every caller, and it does so because of what it reads, not because it is a hook. ## Saying it well in an interview A crisp answer names all three parts: the counters are independent; the reason is that `useState` inside the hook attaches to the calling component, so a hook duplicates behaviour rather than instances; and the fix, if sharing is what you wanted, is a single owner — lifted state, context, or an external store — wrapped in a hook for ergonomics.
- How would you change things so both components really do share one counter?Give the value a single owner and let the hook read it. The lightest option is lifting the `useState` into the nearest common ancestor and passing it down. If the components are far apart, one provider owns the state and both consume it through context. If it is app-wide and updated often, one external store owns it and every component subscribes. The custom hook then wraps the read, not the `useState`.
- What happens if a custom hook keeps a variable at module scope instead of in state?That variable is shared by every caller in the app, because it belongs to the module, not to a component. Worse, writing to it does not schedule a render, so callers can silently disagree about what the current value is. If a hook must expose a shared external value, all callers need to subscribe to it so they re-render together.
- Does calling the same custom hook twice in one component cause problems?No — you simply get two independent instances, which is often exactly what you want, for example two counters or two independent form fields. React matches hook state by call order within the component, not by hook name or arguments, which is also why the calls must happen unconditionally on every render.
saying these in an interview costs you the question
- Thinks two components calling one hook share its state
- Says React deduplicates identical hook calls by name
- Believes a custom hook is a singleton per application
- Claims calling the same hook twice in a component is illegal
- Uses a module-scope variable and expects it to trigger re-renders