Building Your Learning Module...
Getting things ready for you!
Find videos you like?
Save to resource drawer for future reference!
useSyncExternalStore is a Hook that lets you subscribe to external data sources and keep React components synchronized with state that lives outside React. It's perfect for integrating with state management libraries, browser APIs, and third-party stores.
Integrate with Redux, Zustand, MobX, or custom state management solutions
Subscribe to localStorage, geolocation, online status, media queries, and other browser APIs
Connect to WebSocket, WebRTC, or other real-time data sources
Use useSyncExternalStore when you need to sync React components with state that changes outside of React's control.
First, create an external store with subscription capabilities. The store needs to let components subscribe to changes and get the current value.
Pro Tip
The subscribe function must return an unsubscribe function. React will call it when the component unmounts.
Create a function that returns the current snapshot of the store. This function must be pure and return the same value when the store hasn't changed.
Pure Function!
getSnapshot must be pure - same input always returns same output. No side effects allowed!
Now use useSyncExternalStore in your React component to subscribe to the external store and keep it synchronized.
Auto Cleanup!
React automatically calls the unsubscribe function when the component unmounts. No manual cleanup needed!
For server-side rendering, add a third parameter to provide the initial snapshot value. This prevents hydration mismatches.
SSR Safe!
getServerSnapshot ensures the same value is rendered on server and client, preventing hydration errors.
Follow these guidelines to use useSyncExternalStore effectively and avoid common pitfalls.
Golden Rule
getSnapshot must return the same reference when the data hasn't changed to avoid unnecessary re-renders.
Sandbox-safe theme switcher with system preference sync
// External store for theme management (sandbox-safe version)
const themeStore = {
theme: 'light',
listeners: [],
subscribe(callback) {
this.listeners.push(callback);
// Listen for system theme changes
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
if (mediaQuery.addEventListener) {
mediaQuery.addEventListener('change', this.handleSystemThemeChange);
} else {
mediaQuery.addListener(this.handleSystemThemeChange);
}
return () => {
this.listeners = this.listeners.filter(l => l !== callback);
if (mediaQuery.removeEventListener) {
mediaQuery.removeEventListener('change', this.handleSystemThemeChange);
} else {
mediaQuery.removeListener(this.handleSystemThemeChange);
}
};
},
getSnapshot() {
return this.theme;
},
getServerSnapshot() {
return 'light'; // Default for SSR
},
setTheme(newTheme) {
this.theme = newTheme;
// Note: In sandboxed environments, we can't use localStorage
// Theme will only persist for the current session
console.log('Theme set to:', newTheme);
this.listeners.forEach(callback => callback());
},
handleSystemThemeChange: () => {
// For demo purposes, we'll just update based on system preference
// In a real app with localStorage, you'd check saved preference first
const systemTheme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
if (themeStore.theme === 'system') {
themeStore.theme = systemTheme;
themeStore.listeners.forEach(callback => callback());
}
},
initialize() {
// Initialize with system preference (no localStorage access)
this.theme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
}
};
// Initialize store
themeStore.initialize();
// Custom hook for theme
function useTheme() {
const theme = React.useSyncExternalStore(
themeStore.subscribe.bind(themeStore),
themeStore.getSnapshot.bind(themeStore),
themeStore.getServerSnapshot.bind(themeStore)
);
return {
theme,
setTheme: themeStore.setTheme.bind(themeStore),
isDark: theme === 'dark',
isLight: theme === 'light'
};
}
// Theme toggle component
function ThemeToggle() {
const { theme, setTheme, isDark } = useTheme();
return (
<div className="theme-switcher">
<div className="current-theme">
<h2>🎨 Current Theme: {theme}</h2>
<p className="theme-description">
{isDark ? '🌙 Dark mode is active' : '☀️ Light mode is active'}
</p>
</div>
<div className="theme-options">
<button
className={`theme-btn ${theme === 'light' ? 'active' : ''}`}
onClick={() => setTheme('light')}
>
☀️ Light
</button>
<button
className={`theme-btn ${theme === 'dark' ? 'active' : ''}`}
onClick={() => setTheme('dark')}
>
🌙 Dark
</button>
<button
className={`theme-btn ${theme === 'system' ? 'active' : ''}`}
onClick={() => setTheme('system')}
>
💻 System
</button>
</div>
<div className="demo-content">
<h3>Preview Content</h3>
<p>This content adapts to your theme selection.</p>
<div className="demo-box">
<span className="demo-text">Theme-aware box</span>
</div>
</div>
<div className="sync-info">
<h3>🔄 Sync Information</h3>
<ul>
<li>✅ Session-based theme storage</li>
<li>✅ Follows system preference</li>
<li>⚠️ No localStorage (sandboxed environment)</li>
<li>✅ Real-time updates</li>
<li>✅ Sandbox-safe implementation</li>
</ul>
</div>
</div>
);
}
// Apply theme to app container (playground-safe)
function ThemeProvider({ children }) {
const { theme } = useTheme();
React.useEffect(() => {
// Try to apply to document if available, otherwise apply to container
try {
if (document && document.documentElement) {
document.documentElement.setAttribute('data-theme', theme);
document.documentElement.className = theme;
}
} catch (error) {
// Fallback: apply theme to the app container
const appElement = document.querySelector('.app') || document.querySelector('#root');
if (appElement) {
appElement.setAttribute('data-theme', theme);
appElement.className = theme;
}
}
}, [theme]);
return <div className={`app ${theme}`} data-theme={theme}>{children}</div>;
}
function App() {
return (
<ThemeProvider>
<div className="container">
<h1>🎨 Theme Switcher Demo</h1>
<p>Experience seamless theme synchronization with useSyncExternalStore</p>
<ThemeToggle />
</div>
</ThemeProvider>
);
}
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);Loading preview...
Subscribe to external changes and automatically update React components when data changes.
Server-side rendering support with getServerSnapshot prevents hydration mismatches.
Prevents tearing and ensures consistent state across React's concurrent rendering features.