0
# use-sync-external-store
1
2
use-sync-external-store is a backwards-compatible shim for React's useSyncExternalStore hook that enables synchronization with external data stores across React versions. It provides seamless integration with external stores while maintaining compatibility with React 16.8+ through React 19+, offering both native React 18+ performance and fallback implementations for older versions.
3
4
## Package Information
5
6
- **Package Name**: use-sync-external-store
7
- **Package Type**: npm
8
- **Language**: JavaScript/TypeScript (Flow annotated)
9
- **Installation**: `npm install use-sync-external-store`
10
11
## Core Imports
12
13
For **React 18+** (uses native implementation):
14
15
```javascript
16
import { useSyncExternalStore } from 'use-sync-external-store';
17
import { useSyncExternalStoreWithSelector } from 'use-sync-external-store/with-selector';
18
```
19
20
For **React 16.8-17.x** compatibility (uses shim implementation):
21
22
```javascript
23
import { useSyncExternalStore } from 'use-sync-external-store/shim';
24
import { useSyncExternalStoreWithSelector } from 'use-sync-external-store/shim/with-selector';
25
```
26
27
For **React Native** (uses shim implementation optimized for React Native):
28
29
```javascript
30
// Automatically resolves to React Native version when bundled for React Native
31
import { useSyncExternalStore } from 'use-sync-external-store/shim';
32
import { useSyncExternalStoreWithSelector } from 'use-sync-external-store/shim/with-selector';
33
```
34
35
CommonJS imports:
36
37
```javascript
38
const { useSyncExternalStore } = require('use-sync-external-store');
39
const { useSyncExternalStoreWithSelector } = require('use-sync-external-store/with-selector');
40
```
41
42
CommonJS with shim:
43
44
```javascript
45
const { useSyncExternalStore } = require('use-sync-external-store/shim');
46
const { useSyncExternalStoreWithSelector } = require('use-sync-external-store/shim/with-selector');
47
```
48
49
## Basic Usage
50
51
```javascript
52
import { useSyncExternalStore } from 'use-sync-external-store/shim';
53
54
// Create a simple external store
55
const store = {
56
state: { count: 0 },
57
listeners: new Set(),
58
59
getSnapshot() {
60
return this.state;
61
},
62
63
subscribe(callback) {
64
this.listeners.add(callback);
65
return () => this.listeners.delete(callback);
66
},
67
68
setState(newState) {
69
this.state = { ...this.state, ...newState };
70
this.listeners.forEach(callback => callback());
71
}
72
};
73
74
// Use in a React component
75
function Counter() {
76
const state = useSyncExternalStore(
77
store.subscribe.bind(store),
78
store.getSnapshot.bind(store)
79
);
80
81
return (
82
<div>
83
<p>Count: {state.count}</p>
84
<button onClick={() => store.setState({ count: state.count + 1 })}>
85
Increment
86
</button>
87
</div>
88
);
89
}
90
```
91
92
## Architecture
93
94
The package provides two main hooks with multiple implementation strategies:
95
96
- **Native Implementation**: Direct re-export of React 18+ built-in `useSyncExternalStore`
97
- **Shim Implementation**: Custom implementation for React 16.8-17.x with identical API
98
- **Selector Optimization**: Enhanced version with selector functions to minimize re-renders
99
- **Environment Detection**: Automatic server/client and React Native environment handling through conditional exports and runtime detection
100
- **Automatic Fallback**: The shim automatically detects if React's built-in `useSyncExternalStore` is available and uses it, otherwise falls back to custom implementation
101
- **Cross-version Compatibility**: Single codebase supporting React 16.8 through 19+
102
103
## Capabilities
104
105
### Core Store Synchronization
106
107
Basic hook for synchronizing with external stores, providing automatic subscription management and React concurrent features compatibility.
108
109
```typescript { .api }
110
/**
111
* Subscribes to an external store and returns its current snapshot
112
* @param subscribe - Function that registers a callback for store changes, returns unsubscribe function
113
* @param getSnapshot - Function that returns the current snapshot of the store
114
* @param getServerSnapshot - Optional function that returns server-side snapshot for SSR
115
* @returns Current value of the external store
116
*/
117
function useSyncExternalStore<Snapshot>(
118
subscribe: (onStoreChange: () => void) => () => void,
119
getSnapshot: () => Snapshot,
120
getServerSnapshot?: () => Snapshot
121
): Snapshot;
122
```
123
124
**Usage Examples:**
125
126
```javascript
127
import { useSyncExternalStore } from 'use-sync-external-store/shim';
128
129
// Basic store synchronization
130
function useCounterStore() {
131
return useSyncExternalStore(
132
counterStore.subscribe,
133
counterStore.getSnapshot
134
);
135
}
136
137
// With server-side rendering support
138
function useAuthStore() {
139
return useSyncExternalStore(
140
authStore.subscribe,
141
authStore.getSnapshot,
142
() => ({ user: null, isAuthenticated: false }) // SSR fallback
143
);
144
}
145
146
// Window resize example
147
function useWindowSize() {
148
return useSyncExternalStore(
149
(callback) => {
150
window.addEventListener('resize', callback);
151
return () => window.removeEventListener('resize', callback);
152
},
153
() => ({ width: window.innerWidth, height: window.innerHeight }),
154
() => ({ width: 0, height: 0 }) // SSR safe
155
);
156
}
157
```
158
159
### Optimized Store Synchronization with Selector
160
161
Enhanced version that accepts a selector function to extract specific data from the store, minimizing re-renders when only selected portions change.
162
163
```typescript { .api }
164
/**
165
* Subscribes to an external store with selector optimization
166
* @param subscribe - Function that registers a callback for store changes
167
* @param getSnapshot - Function that returns the current snapshot of the store
168
* @param getServerSnapshot - Server-side snapshot function or null
169
* @param selector - Function to extract specific data from snapshot
170
* @param isEqual - Optional equality comparison function for selections
171
* @returns Selected value from the external store
172
*/
173
function useSyncExternalStoreWithSelector<Snapshot, Selection>(
174
subscribe: (onStoreChange: () => void) => () => void,
175
getSnapshot: () => Snapshot,
176
getServerSnapshot: (() => Snapshot) | null | undefined,
177
selector: (snapshot: Snapshot) => Selection,
178
isEqual?: (a: Selection, b: Selection) => boolean
179
): Selection;
180
```
181
182
**Usage Examples:**
183
184
```javascript
185
import { useSyncExternalStoreWithSelector } from 'use-sync-external-store/shim/with-selector';
186
187
// Select specific user data to minimize re-renders
188
function useCurrentUserId() {
189
return useSyncExternalStoreWithSelector(
190
userStore.subscribe,
191
userStore.getSnapshot,
192
null,
193
(state) => state.currentUser?.id,
194
(a, b) => a === b
195
);
196
}
197
198
// Select and transform data
199
function useActiveUsers() {
200
return useSyncExternalStoreWithSelector(
201
userStore.subscribe,
202
userStore.getSnapshot,
203
() => [],
204
(state) => state.users.filter(user => user.isActive),
205
(a, b) => a.length === b.length && a.every((user, i) => user.id === b[i].id)
206
);
207
}
208
209
// Shopping cart total example
210
function useCartTotal() {
211
return useSyncExternalStoreWithSelector(
212
cartStore.subscribe,
213
cartStore.getSnapshot,
214
() => ({ items: [] }),
215
(state) => state.items.reduce((total, item) => total + item.price * item.quantity, 0)
216
);
217
}
218
```
219
220
## Types
221
222
```typescript { .api }
223
/**
224
* Store subscription function type
225
* Registers a callback to be called when store changes
226
* Must return an unsubscribe function
227
*/
228
type Subscribe = (onStoreChange: () => void) => () => void;
229
230
/**
231
* Snapshot getter function type
232
* Returns the current state of the external store
233
*/
234
type GetSnapshot<T> = () => T;
235
236
/**
237
* Server snapshot getter function type
238
* Returns server-safe snapshot for SSR
239
*/
240
type GetServerSnapshot<T> = () => T;
241
242
/**
243
* Selector function type
244
* Extracts specific data from store snapshot
245
*/
246
type Selector<Snapshot, Selection> = (snapshot: Snapshot) => Selection;
247
248
/**
249
* Equality comparison function type
250
* Compares two selected values for equality
251
*/
252
type IsEqual<Selection> = (a: Selection, b: Selection) => boolean;
253
```
254
255
## Environment Support
256
257
### React Native
258
259
The package includes React Native-specific implementations:
260
261
```javascript
262
// Automatically resolves to React Native version on React Native
263
import { useSyncExternalStore } from 'use-sync-external-store/shim';
264
```
265
266
### Server-Side Rendering
267
268
Both hooks support SSR through the `getServerSnapshot` parameter:
269
270
```javascript
271
function useClientOnlyStore() {
272
return useSyncExternalStore(
273
store.subscribe,
274
store.getSnapshot,
275
() => null // Safe server fallback
276
);
277
}
278
```
279
280
### Development vs Production
281
282
The package provides optimized production builds with development warnings removed:
283
284
- Development: Comprehensive error messages and warnings, including detection warnings for unsupported React versions
285
- Production: Optimized bundle size with minimal runtime overhead and all debug code stripped out
286
287
### Implementation Notes
288
289
The shim implementation for older React versions uses a carefully crafted approach that respects React's rules while providing the same API:
290
291
- Uses `useState` and `useEffect` internally to manage subscriptions and state updates
292
- Implements manual snapshot comparison using `Object.is` to detect changes
293
- Handles server-side rendering by falling back to client-side implementation patterns
294
- Maintains referential stability of callback functions to prevent unnecessary re-subscriptions
295
296
## Error Handling
297
298
**Development Mode Warnings:**
299
300
- Using main entry point (`use-sync-external-store`) without React 18+ shows a detailed console error explaining migration to `/shim` entry point
301
- The warning specifically states: "The main 'use-sync-external-store' entry point is not supported; all it does is re-export useSyncExternalStore from the 'react' package, so it only works with React 18+. If you wish to support React 16 and 17, import from 'use-sync-external-store/shim' instead."
302
- Inconsistent snapshots between renders trigger development-only errors in the shim implementation
303
304
**Common Patterns:**
305
306
```javascript
307
// Safe error handling with fallbacks
308
function useSafeExternalStore(store, fallback) {
309
try {
310
return useSyncExternalStore(
311
store.subscribe,
312
store.getSnapshot,
313
() => fallback
314
);
315
} catch (error) {
316
console.warn('Store synchronization failed:', error);
317
return fallback;
318
}
319
}
320
```
321
322
## Migration Guide
323
324
### From React 18+ Native Hook
325
326
Replace direct React imports:
327
328
```javascript
329
// Before
330
import { useSyncExternalStore } from 'react';
331
332
// After (for cross-version compatibility)
333
import { useSyncExternalStore } from 'use-sync-external-store/shim';
334
```
335
336
### From Legacy State Management
337
338
Convert class-based or subscription patterns:
339
340
```javascript
341
// Legacy subscription pattern
342
class LegacyStore {
343
constructor() {
344
this.state = initialState;
345
this.listeners = [];
346
}
347
348
subscribe(callback) {
349
this.listeners.push(callback);
350
return () => {
351
const index = this.listeners.indexOf(callback);
352
if (index > -1) this.listeners.splice(index, 1);
353
};
354
}
355
356
getSnapshot() {
357
return this.state;
358
}
359
}
360
361
// Modern usage
362
function useModernStore() {
363
return useSyncExternalStore(
364
legacyStore.subscribe.bind(legacyStore),
365
legacyStore.getSnapshot.bind(legacyStore)
366
);
367
}
368
```