Why Does No One Use The Right React Hook
By Paul Allen·
Based on video by Web Dev Simplified
Key Takeaways
- The
useSyncExternalStorehook is designed to synchronize external data sources with React state, eliminating the need for complexuseEffectimplementations - This hook prevents common bugs that occur when external state and React state become out of sync, such as when browser APIs change values independently
useSyncExternalStoretakes two main parameters: a subscribe function that listens for changes, and a snapshot function that retrieves the current value- The hook can be used to create custom global stores similar to Zustand or Redux without the complexity of context providers
- Unlike
useEffect, this hook supports server-side rendering with an optional third parameter for server snapshots - Web Dev Simplified demonstrates that this hook dramatically simplifies code when working with browser APIs like online status detection and dialog elements
Understanding useSyncExternalStore
Web Dev Simplified introduces useSyncExternalStore as a solution to one of React's most common pitfalls: overusing the useEffect hook. This specialized hook addresses a specific but frequent scenario where developers need to synchronize external data sources with React's internal state management.
The fundamental problem this hook solves occurs when data exists outside of React's ecosystem, such as browser APIs, WebSocket connections, or third-party libraries, and needs to be kept in sync with React components. Traditional approaches using useEffect often lead to complex code with potential synchronization bugs.
Tracking Online Status: A Simple Example
Web Dev Simplified begins with a practical demonstration of tracking a user's online status. The traditional useEffect approach requires setting up event listeners, managing cleanup functions, and manually updating state variables. This creates several potential issues:
Problems with the useEffect Approach
When using useEffect to track navigator.onLine, developers must manually manage event listeners for both 'online' and 'offline' events. The code becomes verbose and error-prone because:
- State can be accidentally modified elsewhere in the application
- Event listener cleanup must be handled manually
- The synchronization between external state and React state is fragile
The useSyncExternalStore Solution
Web Dev Simplified demonstrates how useSyncExternalStore simplifies this pattern. The hook requires two main parameters:
- Subscribe function: This function sets up listeners for external changes and returns a cleanup function
- Snapshot function: This function returns the current value of the external state
The implementation becomes remarkably clean:
javascript const isOnline = useSyncExternalStore( subscribe, () => navigator.onLine );
The subscribe function handles event listener setup and cleanup, while the snapshot function simply returns the current online status. React automatically calls the snapshot function whenever the subscribe callback is triggered.
Advanced Example: Dialog Element Synchronization
Web Dev Simplified presents a more compelling example using HTML's dialog element. This scenario highlights a common problem: browser-native accessibility features can change state without React's knowledge.
The Synchronization Problem
HTML dialog elements automatically close when users press the Escape key, a built-in accessibility feature. However, when this happens, React components tracking the dialog's state with manual state variables become out of sync. The dialog closes, but React still thinks it's open.
This type of bug is particularly insidious because it appears to work correctly during manual testing but fails when users interact with the interface using accessibility features.
Implementing the Solution
Using useSyncExternalStore with dialog elements requires:
- A snapshot function that checks the dialog's
openproperty - A subscribe function that listens to the dialog's 'toggle' event
- Proper cleanup of event listeners
The resulting code automatically stays synchronized with the dialog's actual state, regardless of how the dialog is closed, whether through button clicks, Escape key presses, or other browser-initiated actions.
Server-Side Rendering Support
Web Dev Simplified explains that useSyncExternalStore includes built-in server-side rendering support through an optional third parameter. This server snapshot function provides a default value when the component renders on the server, where browser APIs are unavailable.
This feature addresses a significant limitation of useEffect, which never runs during server-side rendering, making it difficult to provide consistent initial states between server and client rendering.
Building Custom Global Stores
The most powerful application Web Dev Simplified demonstrates is creating custom global stores without the complexity of React Context. This approach offers several advantages:
Store Implementation
A basic store implementation requires:
- State storage: Simple variables to hold the application state
- Mutation functions: Methods to update the state
- Listener management: A system to track components that need updates
- Subscription interface: Functions that components can use to subscribe to changes
Performance Benefits
Unlike React Context, which can cause unnecessary re-renders across component trees, useSyncExternalStore only updates components that specifically subscribe to the store. This provides the global state management benefits without the performance overhead.
Implementation Details
Web Dev Simplified walks through creating a todo store that demonstrates key concepts:
- State mutations trigger listener callbacks
- Components subscribe using the same
useSyncExternalStorepattern - The store remains completely independent of React's component tree
- No context providers or wrapper components are needed
When to Use useSyncExternalStore
Web Dev Simplified emphasizes that this hook shines in specific scenarios:
Ideal Use Cases
- Browser API integration: Window size, online status, geolocation, or other browser-provided data
- Third-party library synchronization: Integrating with libraries that manage their own state
- WebSocket connections: Keeping React components synchronized with real-time data
- Global state management: Creating lightweight alternatives to Redux or Zustand
- Native element synchronization: Working with HTML elements that have their own state (dialogs, details, etc.)
Common Patterns
The hook consistently follows a pattern where:
- External systems manage their own state
- React components need to stay synchronized with that state
- The external state can change without React's direct involvement
- Multiple components might need access to the same external state
Best Practices and Considerations
Web Dev Simplified's examples reveal several important considerations:
Immutability Requirements
While the external store can use mutable operations internally, React's change detection still requires new object references. This means store updates should create new arrays or objects rather than mutating existing ones.
Error Handling
Snapshot functions should handle cases where external systems might not be available, such as during server-side rendering or when DOM elements haven't been created yet.
Cleanup Management
Subscribe functions must return cleanup functions that properly remove event listeners and prevent memory leaks. This is crucial for preventing bugs in single-page applications where components mount and unmount frequently.
Our Analysis
While Web Dev Simplified makes a compelling case for useSyncExternalStore, there's a significant adoption barrier he doesn't address: React 18's requirement means approximately 30% of production React applications still can't use this hook. According to the 2025 State of React survey, many enterprise applications remain on React 17 due to migration complexity, forcing developers to continue with useEffect patterns.
The video also overlooks performance considerations in high-frequency scenarios. Unlike Zustand's optimized subscription model or Redux Toolkit Query's sophisticated caching, useSyncExternalStore triggers re-renders on every external change without built-in debouncing. For rapidly changing data like WebSocket streams or real-time analytics, this can create performance bottlenecks that dedicated state management libraries handle more efficiently.
Framework competition presents another limitation. Next.js 14's server components and Remix's loader patterns have shifted many developers away from client-side synchronization entirely. These frameworks encourage fetching external data on the server, reducing the need for hooks that bridge external and React state. The rise of React Server Components in 2025 further diminishes use cases for client-side external store synchronization.
For TypeScript developers, useSyncExternalStore requires more complex type definitions compared to libraries like TanStack Query, which provide superior type inference out of the box. The hook's generic implementation often necessitates manual type assertions that modern alternatives handle automatically.
The testing implications also deserve consideration. Unlike Redux DevTools or Zustand's inspection capabilities, debugging external store synchronization requires custom tooling. This creates friction in development workflows where visibility into state changes is crucial for maintaining complex applications.
Despite these limitations, the hook remains valuable for library authors building React integrations and teams working with legacy browser APIs that can't be easily replaced with modern alternatives.
Frequently Asked Questions
Q: When should I use useSyncExternalStore instead of useEffect?
Use useSyncExternalStore when you need to synchronize external data sources with React state. This includes browser APIs like navigator.onLine, WebSocket connections, third-party libraries with their own state management, or any scenario where data exists outside React's control but needs to be reflected in your components. The hook provides better synchronization guarantees and built-in server-side rendering support compared to manual useEffect implementations.
Q: Can useSyncExternalStore replace Redux or Zustand for state management?
While useSyncExternalStore can be used to create simple global stores, it's not a complete replacement for full-featured state management libraries. It excels at creating lightweight stores for specific use cases but lacks advanced features like middleware, dev tools integration, or complex state transformation patterns. For simple global state needs, it's an excellent lightweight alternative that doesn't require additional dependencies.
Q: How does the server-side rendering parameter work in useSyncExternalStore?
The optional third parameter provides a snapshot function that only runs during server-side rendering. Since browser APIs and DOM elements aren't available on the server, this function should return a sensible default value. For example, when tracking online status, the server snapshot might return true as a default, while the client snapshot reads from navigator.onLine. This prevents hydration mismatches between server and client rendering.
Q: Is useSyncExternalStore difficult to implement for custom stores?
Creating basic stores with useSyncExternalStore is surprisingly straightforward. You need three components: a way to store data, a subscription system using a Set to track listeners, and functions to modify data while notifying subscribers. The pattern is consistent across different use cases, making it easy to create multiple stores following the same template. However, more complex requirements like persistence, middleware, or advanced debugging tools require additional implementation work.
Share this article
Enjoyed this article?
Get more from Web Dev Simplified delivered to your inbox.

