React Curriculum

useSyncExternalStore: Tearing Prevention

Hard100% Free~30 mins#react#usesyncexternalstore#tearing#concurrent-mode#external-stores#zustand
Interview Answer (30-Second Summary)

useSyncExternalStore safely subscribes components to external non-React state stores (browser APIs, Redux, Zustand) without concurrent tearing. It forces synchronous store reads during render and requires subscribe, getSnapshot, and getServerSnapshot callbacks.

Key Learning Objectives
✓

Explain the "tearing" visual defect in Concurrent React when reading mutable external stores.

✓

Implement custom browser subscriptions (window.navigator.onLine, matchMedia) using useSyncExternalStore.

✓

Understand the required contracts: subscribe callback, getSnapshot immutable return, and getServerSnapshot.

✓

Explain why library authors (Redux, Zustand) migrated from useEffect subscriptions to useSyncExternalStore.

The Interview Problem & Context

What is "tearing" in Concurrent React? Implement a custom hook useOnlineStatus() using useSyncExternalStore that subscribes to browser network status without tearing.

1import { useSyncExternalStore } from 'react';
2
3// 1. Subscribe function: attaches listeners and returns cleanup
4function subscribe(callback: () => void) {
5 window.addEventListener('online', callback);
6 window.addEventListener('offline', callback);
7 return () => {
8 window.removeEventListener('online', callback);
9 window.removeEventListener('offline', callback);
10 };
11}
12
13// 2. Snapshot function: returns current immutable value
14function getSnapshot() {
15 return navigator.onLine;
16}
17
18// 3. Server snapshot function: for SSR hydration safety
19function getServerSnapshot() {
20 return true; // Default to online during server render
21}
22
23export function useOnlineStatus() {
24 return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
25}
Deep Architectural Explanation

The Concurrent Tearing Problem

In synchronous React (React 17), rendering was uninterruptible.
In Concurrent React (React 18+), rendering can be paused and resumed across multiple time slices.

What is Tearing?

Imagine an external store (like Redux or a global variable) with color = "blue":

  1. Component A renders with color = "blue".
  2. React yields the thread to the browser.
  3. A user action or WebSocket event modifies the external store: color = "red"!
  4. React resumes rendering Component B with color = "red".
    Visual Tearing: Component A is blue and Component B is red on the exact same screen for the same store data! The UI is torn and inconsistent.

How useSyncExternalStore Fixes Tearing

useSyncExternalStore tells React:

  • This state lives outside React's Fiber architecture.
  • Whenever this store changes, React checks the snapshot. If the store changed mid-render, React discards the in-progress concurrent render and restarts the render pass synchronously, guaranteeing UI consistency!

The getSnapshot Invariant

getSnapshot must return a cached or immutable value. If you return a new object:

ts
// ❌ INFINITE LOOP: Returns a new object reference on every call!
function getSnapshot() { return { online: navigator.onLine }; }

React will detect a "changed" snapshot on every check, triggering an infinite re-render loop!

Common Interview Traps & Pitfalls
✕

Returning a brand new object or array literal from getSnapshot(), causing infinite re-render loops.

✕

Omitting the third argument (getServerSnapshot) in server-rendered applications.

✕

Using useEffect to subscribe to external stores in React 18 instead of useSyncExternalStore.

Senior Interviewer Follow-Up Probes
?

Why did Zustand and Redux rewrite their core subscription mechanisms around useSyncExternalStore for React 18?

?

What happens if getSnapshot returns different values on consecutive calls without an external event firing?

?

How does useSyncExternalStore handle selector equality functions?

Frequently Asked Interview Questions
What is "tearing" in Concurrent React?

Tearing occurs when different components in the same render pass read different values from a mutable external store that changed mid-render, resulting in an inconsistent UI.

Because useEffect runs asynchronously after the paint phase. Between the render phase and the effect execution, the external store could mutate, causing visual tearing.

React compares getSnapshot() returns using Object.is. Returning a new object reference triggers React to assume the store changed, causing an infinite re-render loop.

It provides a deterministic snapshot during server-side rendering (where window or navigator APIs do not exist), preventing hydration mismatch errors.

Yes. It is the recommended modern React API for subscribing to window resize, matchMedia, geolocation, or online status.