Engineering Deep Dive

Fixing useSyncExternalStore Tearing in Concurrent React Apps

Struggling with UI tearing in your React apps? Learn how to correctly implement useSyncExternalStore to maintain consistency during concurrent transitions.

Krapton Engineering
Reviewed by a senior engineer4 min read
Share
Fixing useSyncExternalStore Tearing in Concurrent React Apps

In modern React applications, state management has moved beyond simple useState hooks. As we push for higher performance with concurrent rendering, we often integrate external stores—like Redux, Zustand, or custom event emitters—directly into the React lifecycle. However, a common frustration for senior engineers is the dreaded "tearing" effect, where the UI displays inconsistent data because the external store updated during a startTransition process.

TL;DR: React's useSyncExternalStore hook is the only safe way to bridge external state with concurrent React. If your UI is showing mismatched data during state updates, you are likely missing the getSnapshot memoization or the subscribe function is not properly handling store mutations.

Key takeaways

A detailed close-up photograph of a frayed nautical jute rope against a soft blue background.
Photo by Mariya Muschard on Pexels
  • Tearing occurs when React renders different parts of the tree based on different versions of an external store.
  • useSyncExternalStore forces React to treat external state as a single, consistent source of truth.
  • Always memoize your getSnapshot function to prevent unnecessary re-renders.
  • The subscribe function must return a cleanup function to prevent memory leaks in production.

The Problem: Why UI Tearing Happens

White paper torn with an empty center, perfect for abstract designs.
Photo by Beyzanur K. on Pexels

In concurrent mode, React can pause, resume, or even discard work. If an external store (like a global variable or a WebSocket-backed object) mutates while React is in the middle of rendering a component tree, different components might read different versions of that state. This is called tearing.

Before useSyncExternalStore was introduced, developers often relied on useEffect or useLayoutEffect to sync state. These approaches fail in concurrent rendering because they don't inform React that the state might change during the render phase. When we see this in production, it often manifests as flickering UI elements or "content jumping" where a user name updates before the associated ID, causing a temporary data mismatch.

Implementing useSyncExternalStore Correctly

The hook requires three arguments: subscribe, getSnapshot, and an optional getServerSnapshot. The key to avoiding the tearing issue is ensuring that getSnapshot returns an immutable value or a value that is stable between renders.

import { useSyncExternalStore } from 'react';

// Your external store
const store = {
  state: { count: 0 },
  listeners: new Set(),
  subscribe(listener) {
    this.listeners.add(listener);
    return () => this.listeners.delete(listener);
  },
  getSnapshot() {
    return this.state;
  },
  increment() {
    this.state = { count: this.state.count + 1 };
    this.listeners.forEach(l => l());
  }
};

function Counter() {
  const snapshot = useSyncExternalStore(
    store.subscribe.bind(store),
    store.getSnapshot.bind(store)
  );

  return <div>Count: {snapshot.count}</div>;
}

Common Pitfalls in 2026

In our recent work with high-frequency data dashboards, we observed teams making the mistake of defining the getSnapshot function inline. This causes React to think the store has changed on every render, effectively disabling the performance benefits of concurrent rendering and potentially re-triggering effects unnecessarily.

The "Inline Function" Anti-Pattern

Avoid this pattern at all costs:

// DON'T DO THIS
const snapshot = useSyncExternalStore(
  subscribe,
  () => store.getState() // New function reference every render!
);

By passing a new function reference every time, you break the internal comparison logic React uses to determine if the state has actually changed. Always define your getSnapshot outside the component or wrap it in useCallback if it depends on component props.

Comparison: Legacy vs. Modern Synchronization

ApproachSafetyConcurrent SupportComplexity
useEffect SyncLow (Risk of tearing)NoHigh
useSyncExternalStoreHigh (Guaranteed)YesLow
Custom Event ListenersMediumNoMedium

When NOT to use this approach

While useSyncExternalStore is powerful, it is not a silver bullet for every state problem. If your state is strictly local to a component or can be lifted into standard React state (using useState or useReducer), do not reach for an external store. Introducing an external dependency adds unnecessary abstraction and overhead. Use this hook only when you are integrating with libraries like Redux, Zustand, or custom WebSocket implementations that exist outside the React tree.

FAQ

Does useSyncExternalStore work with Server Side Rendering?

Yes, but you must provide the getServerSnapshot parameter. This ensures that the server renders the initial state correctly before the hydration process begins on the client, preventing content mismatch errors.

Why is my store tearing even with useSyncExternalStore?

Usually, this happens because the getSnapshot function returns a mutable object that you are modifying directly. Always return a new object (shallow copy) when the state updates to ensure React detects the change correctly.

Can I use this for non-React state?

Technically, yes, but it is purpose-built for React. If you need to sync state between non-React parts of your application, standard EventTarget or CustomEvent patterns are more idiomatic and performant.

Need Expert React Implementation?

Managing concurrent state transitions and preventing UI tearing requires a deep understanding of React's internal fiber architecture. If your team is struggling with complex state synchronization or performance bottlenecks, we are here to help. Hire React developers from Krapton to ensure your application is built on a solid, performant foundation. We specialize in scaling complex frontends for high-traffic environments.

About the author

The Krapton engineering team has years of hands-on experience building high-performance web applications using React and Next.js, solving complex state management issues for startups and enterprises worldwide.

javascriptreactperformancedebuggingtutorialhow-tostate-management
About the author

Krapton Engineering

The Krapton engineering team has years of hands-on experience building high-performance web applications using React and Next.js, solving complex state management issues for startups and enterprises worldwide.