or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

index.md

index.mddocs/

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

```