When you integrate a non-React data source such as a browser API or a third-party event emitter into a React 19 app, when should you stop hand-rolling useState plus a useEffect subscription and reach for useSyncExternalStore instead, and what does that change?
answer
- state as a mirror of the outside truth
- is the value actually rendered?
- one consistent value per pass
- what is it on the server?
- the primitive libraries integrate through
basics
~20 sReach for it once the outside value is rendered as state. React then reads the source itself rather than mirroring it into component state, so every component in one render pass sees the same value and no change slips through before a subscription is attached.
solid answer
~60 sThe dividing line is whether the outside value is *rendered*. If an effect only needs to react to a source — send analytics when a media query flips, start and stop a player — a plain subscribe/unsubscribe effect is the right and simplest tool. But the moment you copy the source into state so components can display it, you have created a mirror that can disagree with the original: a change can land in the window between render and the effect, and under concurrent rendering two components reading the mirror at different points in one pass can show different values, which is tearing. `useSyncExternalStore` removes the mirror. React reads the current value itself as part of rendering and re-reads it when your subscription reports a change, so consistency within a pass and synchronisation at subscribe time stop being things each hook author re-implements. The cost is that you must be able to produce a value on the server too, and that the value you hand back must be stable between changes.
go deeper
Know that some data lives outside React — browser APIs, sockets, third-party emitters — and that React has a dedicated hook for reading such a source, distinct from copying its value into component state.
Explain the difference between mirroring an outside value into state and letting React read it, and name what the mirror can get wrong: a stale start and two components disagreeing within one render pass.
Make the call in context. Say which sources in a codebase are rendered and therefore warrant the hook, which are side-effect-only and should stay plain effects, and what the server-rendered value has to be before you adopt it.
Own the boundary. Decide whether outside sources are wrapped once in reviewed store adapters or re-integrated per feature, and treat the requirement to define a server value as a design gate on what may drive server-rendered markup at all.
## What the hand-rolled version really is The familiar pattern — `useState` seeded from an outside source, plus a `useEffect` that subscribes and calls the setter — is a **mirror**. The truth lives outside React; component state holds a copy; the subscription's job is to keep the copy in step. Every problem with the pattern follows from that one structural fact: a copy can be out of date, and two copies can disagree. That framing is worth saying out loud in an interview, because it explains all three failure modes at once instead of listing them as unrelated gotchas. ## The three things a mirror gets wrong **It can start wrong.** The read happens during render; the subscribe happens after commit and paint. A change in that window is never delivered, so the copy is stale until the source changes again — possibly forever. **It can disagree with itself.** React 19 renders concurrently: a render pass can be interrupted and resumed. If two components read a source that changes mid-pass, they can commit different values for the same underlying truth, and the UI shows two answers to one question. That is tearing, and it is the reason `useSyncExternalStore` was introduced rather than being left to userland. **It costs an extra round trip.** A change arrives, the handler sets state, React schedules a render. The value was already available; the mirror just delays it by a render. ## What changes when React reads the source `useSyncExternalStore` inverts the relationship. Instead of you pushing values into state, you hand React a way to be notified and a way to read the current value, and React consults it as part of rendering. The consequences that matter for a decision: - **One value per pass.** React guarantees all consumers in a render see a consistent value, so the tearing case cannot occur by construction. - **Synchronisation is handled.** React checks the store around subscribing, so the window that the hand-rolled version must patch with an extra read after `addEventListener` is closed for you. - **It is the library-facing primitive.** State managers and router/data libraries integrate with React through this hook. If you are writing something other people will consume, use the supported primitive rather than a bespoke mirror whose concurrency behaviour you would then have to defend. ```js const isOnline = useSyncExternalStore(subscribe, () => navigator.onLine); ``` Note what is *not* in that line: no state, no setter, no effect, and nothing to keep in step. ## What it costs you **A server story.** Under server rendering there is no `navigator` and no subscription, so you must be able to produce a value without the browser. Being forced to answer "what is this value on the server?" is itself useful design pressure: if there is no sensible answer, the value probably should not drive server-rendered markup at all, and rendering it after mount is the honest design. **A stable value between changes.** The read React performs must return the same value each time until the source has actually changed. A read that manufactures a fresh object or array every call makes React believe the store changed on every check. This is a real trap when the source's natural shape is a derived object; the store, not the component, should own the memoized value. **A subscribe/unsubscribe contract.** You still supply a subscribe function that returns the way to unsubscribe. Cleanup did not go away; it moved. ## When the hand-rolled effect is still correct Do not turn this into a blanket rule. A plain subscription effect remains the right tool when the source drives a side effect rather than output: pausing media when a tab is hidden, reporting a viewport change to analytics, wiring a third-party widget's events to imperative calls. Nothing is rendered from the value, so there is no mirror and none of the three failure modes applies. Reaching for `useSyncExternalStore` there adds a render dependency that buys nothing. It is also the wrong answer to "should we adopt a state manager?" — this hook is how such a library talks to React, not a substitute for one. And it is not a performance optimisation: it does not reduce render count. Candidates who describe it as "faster" have the wrong model. ## The decision, stated compactly Ask two questions. *Is the value rendered?* If no, use an effect. *Can more than one component read it during a single render pass, or does staleness at mount matter?* If yes, use `useSyncExternalStore` — and if the value also has to appear in server-rendered HTML, decide what it is on the server before you commit to that. Everything else, including which of the hook's arguments is which, is implementation detail beneath a decision you have already made.
- Does moving to useSyncExternalStore mean you no longer need cleanup?No, cleanup just moves. The subscribe function you hand React returns the way to unsubscribe, and React calls it when the component unmounts or when the subscription needs to be re-established. The subscribe/unsubscribe symmetry is identical — it now lives in the store adapter rather than in an effect.
- A widget only fires analytics when a media query flips. Does it need this hook?No. Nothing is rendered from the value, so there is no mirrored copy that could go stale or disagree with another component. A subscribe-in-effect with a matching unsubscribe in cleanup is the simpler and correct tool; adding a render-time dependency would buy nothing.
- Twenty components use the same small store through this hook. Does that mean twenty subscriptions?Yes, each hook call subscribes independently, so the store must make subscribing and unsubscribing cheap and should notify listeners once per change rather than per-listener work. That is a constraint on store design, not something React resolves for you.
- Is this how you would integrate an existing third-party state library?It is the supported seam, yes — libraries expose their own React bindings built on it precisely so consumers do not hand-roll a mirror. If a library offers official hooks, use those; write your own adapter only for a source that has none, such as a browser API or an in-house emitter.
saying these in an interview costs you the question
- Calls useSyncExternalStore a performance optimization that reduces renders
- Says every useEffect subscription is now a bug to be replaced
- Assumes it works unchanged under server rendering with no server value
- Claims it replaces a state manager rather than integrating with one
- Thinks it removes the need to unsubscribe