Skip to content

Commit 1da20dd

Browse files
committed
Implement a "compat" entry point that falls back to the uSES shim
We use the same trick we do with batchedUpdates: a module with variables + getter/setter functions. Each entry point is then responsible for importing the right variables and setting them. This happens before the modules are loaded, so we can then import the getters where they're needed and call them to get the right instances of `useSyncExternalStore`. The "compat" and "alternate-renderer" entry points both use the `uSES` shim, so they should be compatible back to React 16.9+. The primary entry point imports directly from React, and thus assumes React 18.
1 parent fa2c20b commit 1da20dd

File tree

7 files changed

+76
-15
lines changed

7 files changed

+76
-15
lines changed

src/alternate-renderers.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,14 @@
1-
export * from './exports'
1+
// The "alternate renderers" entry point is primarily here to fall back on a no-op
2+
// version of `unstable_batchedUpdates`, for use with renderers other than ReactDOM/RN.
3+
// Examples include React-Three-Fiber, Ink, etc.
4+
// Because of that, we'll also assume the useSyncExternalStore compat shim is needed.
5+
6+
import { useSyncExternalStore } from 'use-sync-external-store/shim'
7+
import { useSyncExternalStoreWithSelector } from 'use-sync-external-store/shim/with-selector'
8+
9+
import { setSyncFunctions } from './utils/useSyncExternalStore'
10+
11+
setSyncFunctions(useSyncExternalStore, useSyncExternalStoreWithSelector)
212

313
import { getBatch } from './utils/batch'
414

@@ -7,3 +17,5 @@ import { getBatch } from './utils/batch'
717
const batch = getBatch()
818

919
export { batch }
20+
21+
export * from './exports'

src/compat.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// The "compat" entry point assumes we're working with standard ReactDOM/RN, but
2+
// older versions that do not include `useSyncExternalStore` (React 16.9 - 17.x).
3+
// Because of that, the useSyncExternalStore compat shim is needed.
4+
5+
import { useSyncExternalStore } from 'use-sync-external-store/shim'
6+
import { useSyncExternalStoreWithSelector } from 'use-sync-external-store/shim/with-selector'
7+
8+
import { setSyncFunctions } from './utils/useSyncExternalStore'
9+
import { unstable_batchedUpdates as batch } from './utils/reactBatchedUpdates'
10+
import { setBatch } from './utils/batch'
11+
12+
setSyncFunctions(useSyncExternalStore, useSyncExternalStoreWithSelector)
13+
14+
// Enable batched updates in our subscriptions for use
15+
// with standard React renderers (ReactDOM, React Native)
16+
setBatch(batch)
17+
18+
export { batch }
19+
20+
export * from './exports'

src/components/Context.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React from 'react'
2-
import { Action, AnyAction, Store } from 'redux'
2+
import type { Action, AnyAction, Store } from 'redux'
33
import type { Subscription } from '../utils/Subscription'
44

55
export interface ReactReduxContextValue<

src/components/connect.tsx

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,6 @@
11
/* eslint-disable valid-jsdoc, @typescript-eslint/no-unused-vars */
22
import hoistStatics from 'hoist-non-react-statics'
3-
import React, {
4-
useContext,
5-
useMemo,
6-
useRef,
7-
useReducer,
8-
// @ts-ignore
9-
useSyncExternalStore,
10-
} from 'react'
3+
import React, { useContext, useMemo, useRef, useReducer } from 'react'
114
import { isValidElementType, isContextConsumer } from 'react-is'
125

136
import type { Store, Dispatch, Action, AnyAction } from 'redux'
@@ -35,6 +28,7 @@ import defaultMergePropsFactories from '../connect/mergeProps'
3528

3629
import { createSubscription, Subscription } from '../utils/Subscription'
3730
import { useIsomorphicLayoutEffect } from '../utils/useIsomorphicLayoutEffect'
31+
import { getSyncFunctions } from '../utils/useSyncExternalStore'
3832
import shallowEqual from '../utils/shallowEqual'
3933

4034
import {
@@ -43,6 +37,8 @@ import {
4337
ReactReduxContextInstance,
4438
} from './Context'
4539

40+
const [useSyncExternalStore] = getSyncFunctions()
41+
4642
// Define some constant arrays just to avoid re-creating these
4743
const EMPTY_ARRAY: [unknown, number] = [null, 0]
4844
const NO_SUBSCRIPTION_ARRAY = [null, null]

src/hooks/useSelector.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import { useContext, useDebugValue } from 'react'
22

3-
// @ts-ignore
4-
import { useSyncExternalStoreWithSelector } from 'use-sync-external-store/with-selector'
5-
63
import { useReduxContext as useDefaultReduxContext } from './useReduxContext'
74
import { ReactReduxContext } from '../components/Context'
8-
import { DefaultRootState, EqualityFn } from '../types'
5+
import { getSyncFunctions } from '../utils/useSyncExternalStore'
6+
import type { DefaultRootState, EqualityFn } from '../types'
7+
8+
const [, useSyncExternalStoreWithSelector] = getSyncFunctions()
99

1010
const refEquality: EqualityFn<any> = (a, b) => a === b
1111

src/index.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,22 @@
1-
export * from './exports'
1+
// The default entry point assumes we are working with React 18, and thus have
2+
// useSyncExternalStore available. We can import that directly from React itself.
3+
// The useSyncExternalStoreWithSelector has to be imported, but we can use the
4+
// non-shim version. This shaves off the byte size of the shim.
5+
6+
// @ts-ignore React types not updated yet
7+
import { useSyncExternalStore } from 'react'
8+
import { useSyncExternalStoreWithSelector } from 'use-sync-external-store/with-selector'
29

10+
import { setSyncFunctions } from './utils/useSyncExternalStore'
311
import { unstable_batchedUpdates as batch } from './utils/reactBatchedUpdates'
412
import { setBatch } from './utils/batch'
513

14+
setSyncFunctions(useSyncExternalStore, useSyncExternalStoreWithSelector)
15+
616
// Enable batched updates in our subscriptions for use
717
// with standard React renderers (ReactDOM, React Native)
818
setBatch(batch)
919

1020
export { batch }
21+
22+
export * from './exports'

src/utils/useSyncExternalStore.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import type { useSyncExternalStore } from 'use-sync-external-store'
2+
import type { useSyncExternalStoreWithSelector } from 'use-sync-external-store/with-selector'
3+
4+
const notInitialized = () => {
5+
throw new Error('Not initialize!')
6+
}
7+
8+
let uSES: typeof useSyncExternalStore = notInitialized
9+
let uSESWS: typeof useSyncExternalStoreWithSelector = notInitialized
10+
11+
// Allow injecting the actual functions from the entry points
12+
export const setSyncFunctions = (
13+
sync: typeof useSyncExternalStore,
14+
withSelector: typeof useSyncExternalStoreWithSelector
15+
) => {
16+
uSES = sync
17+
uSESWS = withSelector
18+
}
19+
20+
// Supply a getter just to skip dealing with ESM bindings
21+
export const getSyncFunctions = () => [uSES, uSESWS] as const

0 commit comments

Comments
 (0)