diff --git a/ReactVersions.js b/ReactVersions.js
index 2ce7680f0191c..bdab35424e885 100644
--- a/ReactVersions.js
+++ b/ReactVersions.js
@@ -36,6 +36,7 @@ const stablePackages = {
'react-refresh': '0.11.0',
'react-test-renderer': ReactVersion,
'use-subscription': '1.6.0',
+ 'use-sync-external-store': '1.0.0',
scheduler: '0.21.0',
};
@@ -47,7 +48,6 @@ const experimentalPackages = [
'react-fs',
'react-pg',
'react-server-dom-webpack',
- 'use-sync-external-store',
];
module.exports = {
diff --git a/packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js b/packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js
index 6937efd631572..087d74d628724 100644
--- a/packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js
+++ b/packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js
@@ -1055,9 +1055,8 @@ describe('ReactHooksInspectionIntegration', () => {
]);
});
- // @gate experimental || www
it('should support composite useSyncExternalStore hook', () => {
- const useSyncExternalStore = React.unstable_useSyncExternalStore;
+ const useSyncExternalStore = React.useSyncExternalStore;
function Foo() {
const value = useSyncExternalStore(
() => () => {},
diff --git a/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js b/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js
index a2ede1746d08a..abbbdc1578753 100644
--- a/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js
+++ b/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js
@@ -18,7 +18,7 @@ let ReactDOMFizzServer;
let Suspense;
let SuspenseList;
let useSyncExternalStore;
-let useSyncExternalStoreExtra;
+let useSyncExternalStoreWithSelector;
let PropTypes;
let textCache;
let window;
@@ -43,11 +43,23 @@ describe('ReactDOMFizzServer', () => {
Stream = require('stream');
Suspense = React.Suspense;
SuspenseList = React.SuspenseList;
- useSyncExternalStore = React.unstable_useSyncExternalStore;
- useSyncExternalStoreExtra = require('use-sync-external-store/extra')
- .useSyncExternalStoreExtra;
+
PropTypes = require('prop-types');
+ if (gate(flags => flags.source)) {
+ // The `with-selector` module composes the main `use-sync-external-store`
+ // entrypoint. In the compiled artifacts, this is resolved to the `shim`
+ // implementation by our build config, but when running the tests against
+ // the source files, we need to tell Jest how to resolve it. Because this
+ // is a source module, this mock has no affect on the build tests.
+ jest.mock('use-sync-external-store/src/useSyncExternalStore', () =>
+ jest.requireActual('react'),
+ );
+ }
+ useSyncExternalStore = React.useSyncExternalStore;
+ useSyncExternalStoreWithSelector = require('use-sync-external-store/with-selector')
+ .useSyncExternalStoreWithSelector;
+
textCache = new Map();
// Test Environment
@@ -1663,7 +1675,6 @@ describe('ReactDOMFizzServer', () => {
);
});
- // @gate supportsNativeUseSyncExternalStore
// @gate experimental
it('calls getServerSnapshot instead of getSnapshot', async () => {
const ref = React.createRef();
@@ -1734,7 +1745,6 @@ describe('ReactDOMFizzServer', () => {
// The selector implementation uses the lazy ref initialization pattern
// @gate !(enableUseRefAccessWarning && __DEV__)
- // @gate supportsNativeUseSyncExternalStore
// @gate experimental
it('calls getServerSnapshot instead of getSnapshot (with selector and isEqual)', async () => {
// Same as previous test, but with a selector that returns a complex object
@@ -1767,7 +1777,7 @@ describe('ReactDOMFizzServer', () => {
}
function App() {
- const {env} = useSyncExternalStoreExtra(
+ const {env} = useSyncExternalStoreWithSelector(
subscribe,
getClientSnapshot,
getServerSnapshot,
@@ -1815,7 +1825,6 @@ describe('ReactDOMFizzServer', () => {
expect(ref.current).toEqual(serverRenderedDiv);
});
- // @gate supportsNativeUseSyncExternalStore
// @gate experimental
it(
'errors during hydration force a client render at the nearest Suspense ' +
@@ -1964,7 +1973,6 @@ describe('ReactDOMFizzServer', () => {
},
);
- // @gate supportsNativeUseSyncExternalStore
// @gate experimental
it(
'errors during hydration force a client render at the nearest Suspense ' +
diff --git a/packages/react-reconciler/src/__tests__/useSyncExternalStore-test.js b/packages/react-reconciler/src/__tests__/useSyncExternalStore-test.js
index d939a02bee897..623ea12755907 100644
--- a/packages/react-reconciler/src/__tests__/useSyncExternalStore-test.js
+++ b/packages/react-reconciler/src/__tests__/useSyncExternalStore-test.js
@@ -36,7 +36,7 @@ describe('useSyncExternalStore', () => {
useImperativeHandle = React.useImperativeHandle;
forwardRef = React.forwardRef;
useRef = React.useRef;
- useSyncExternalStore = React.unstable_useSyncExternalStore;
+ useSyncExternalStore = React.useSyncExternalStore;
startTransition = React.startTransition;
act = require('jest-react').act;
@@ -70,7 +70,6 @@ describe('useSyncExternalStore', () => {
};
}
- // @gate supportsNativeUseSyncExternalStore
test(
'detects interleaved mutations during a concurrent read before ' +
'layout effects fire',
diff --git a/packages/react/index.classic.fb.js b/packages/react/index.classic.fb.js
index 568a8ada613d9..c5854f8f6d398 100644
--- a/packages/react/index.classic.fb.js
+++ b/packages/react/index.classic.fb.js
@@ -54,7 +54,6 @@ export {
useMutableSource,
useMutableSource as unstable_useMutableSource,
useSyncExternalStore,
- useSyncExternalStore as unstable_useSyncExternalStore,
useReducer,
useRef,
useState,
diff --git a/packages/react/index.experimental.js b/packages/react/index.experimental.js
index 7491bbb7e832d..4b4fa89e01898 100644
--- a/packages/react/index.experimental.js
+++ b/packages/react/index.experimental.js
@@ -47,7 +47,7 @@ export {
useLayoutEffect,
useMemo,
useMutableSource as unstable_useMutableSource,
- useSyncExternalStore as unstable_useSyncExternalStore,
+ useSyncExternalStore,
useReducer,
useRef,
useState,
diff --git a/packages/react/index.js b/packages/react/index.js
index 59cc05f0254e6..9a6a99ee52189 100644
--- a/packages/react/index.js
+++ b/packages/react/index.js
@@ -73,7 +73,6 @@ export {
useMemo,
useMutableSource,
useSyncExternalStore,
- useSyncExternalStore as unstable_useSyncExternalStore,
useReducer,
useRef,
useState,
diff --git a/packages/react/index.modern.fb.js b/packages/react/index.modern.fb.js
index cd60ee426fa65..8d08a43b90946 100644
--- a/packages/react/index.modern.fb.js
+++ b/packages/react/index.modern.fb.js
@@ -53,7 +53,6 @@ export {
useMutableSource,
useMutableSource as unstable_useMutableSource,
useSyncExternalStore,
- useSyncExternalStore as unstable_useSyncExternalStore,
useReducer,
useRef,
useState,
diff --git a/packages/react/index.stable.js b/packages/react/index.stable.js
index 4e4682b8fc29f..3a0600d11a713 100644
--- a/packages/react/index.stable.js
+++ b/packages/react/index.stable.js
@@ -40,6 +40,7 @@ export {
useLayoutEffect,
useMemo,
useMutableSource as unstable_useMutableSource,
+ useSyncExternalStore,
useReducer,
useRef,
useState,
diff --git a/packages/use-sync-external-store/index.js b/packages/use-sync-external-store/index.js
index ff57d66d841bc..55548c1126626 100644
--- a/packages/use-sync-external-store/index.js
+++ b/packages/use-sync-external-store/index.js
@@ -9,4 +9,4 @@
'use strict';
-export * from './src/useSyncExternalStore';
+export {useSyncExternalStore} from './src/useSyncExternalStore';
diff --git a/packages/use-sync-external-store/npm/extra.js b/packages/use-sync-external-store/npm/extra.js
deleted file mode 100644
index 468dd518ac1fc..0000000000000
--- a/packages/use-sync-external-store/npm/extra.js
+++ /dev/null
@@ -1,7 +0,0 @@
-'use strict';
-
-if (process.env.NODE_ENV === 'production') {
- module.exports = require('./cjs/use-sync-external-store-extra.production.min.js');
-} else {
- module.exports = require('./cjs/use-sync-external-store-extra.development.js');
-}
diff --git a/packages/use-sync-external-store/npm/index.native.js b/packages/use-sync-external-store/npm/index.native.js
deleted file mode 100644
index 22546b9c0ebba..0000000000000
--- a/packages/use-sync-external-store/npm/index.native.js
+++ /dev/null
@@ -1,7 +0,0 @@
-'use strict';
-
-if (process.env.NODE_ENV === 'production') {
- module.exports = require('./cjs/use-sync-external-store.native.production.min.js');
-} else {
- module.exports = require('./cjs/use-sync-external-store.native.development.js');
-}
diff --git a/packages/use-sync-external-store/npm/shim/index.js b/packages/use-sync-external-store/npm/shim/index.js
new file mode 100644
index 0000000000000..bde36f38efdb3
--- /dev/null
+++ b/packages/use-sync-external-store/npm/shim/index.js
@@ -0,0 +1,7 @@
+'use strict';
+
+if (process.env.NODE_ENV === 'production') {
+ module.exports = require('../cjs/use-sync-external-store-shim.production.min.js');
+} else {
+ module.exports = require('../cjs/use-sync-external-store-shim.development.js');
+}
diff --git a/packages/use-sync-external-store/npm/shim/index.native.js b/packages/use-sync-external-store/npm/shim/index.native.js
new file mode 100644
index 0000000000000..0bd5c7e1c0f85
--- /dev/null
+++ b/packages/use-sync-external-store/npm/shim/index.native.js
@@ -0,0 +1,7 @@
+'use strict';
+
+if (process.env.NODE_ENV === 'production') {
+ module.exports = require('../cjs/use-sync-external-store-shim.native.production.min.js');
+} else {
+ module.exports = require('../cjs/use-sync-external-store-shim.native.development.js');
+}
diff --git a/packages/use-sync-external-store/npm/shim/with-selector.js b/packages/use-sync-external-store/npm/shim/with-selector.js
new file mode 100644
index 0000000000000..1175186c64286
--- /dev/null
+++ b/packages/use-sync-external-store/npm/shim/with-selector.js
@@ -0,0 +1,7 @@
+'use strict';
+
+if (process.env.NODE_ENV === 'production') {
+ module.exports = require('../cjs/use-sync-external-store-shim/with-selector.production.min.js');
+} else {
+ module.exports = require('../cjs/use-sync-external-store-shim/with-selector.development.js');
+}
diff --git a/packages/use-sync-external-store/npm/with-selector.js b/packages/use-sync-external-store/npm/with-selector.js
new file mode 100644
index 0000000000000..9163b3e7e13f7
--- /dev/null
+++ b/packages/use-sync-external-store/npm/with-selector.js
@@ -0,0 +1,7 @@
+'use strict';
+
+if (process.env.NODE_ENV === 'production') {
+ module.exports = require('./cjs/use-sync-external-store-with-selector.production.min.js');
+} else {
+ module.exports = require('./cjs/use-sync-external-store-with-selector.development.js');
+}
diff --git a/packages/use-sync-external-store/package.json b/packages/use-sync-external-store/package.json
index b43b3a0ec67d2..0d1d9b8d25f80 100644
--- a/packages/use-sync-external-store/package.json
+++ b/packages/use-sync-external-store/package.json
@@ -12,8 +12,10 @@
"README.md",
"build-info.json",
"index.js",
- "extra.js",
"index.native.js",
+ "with-selector.js",
+ "with-selector.native.js",
+ "shim/",
"cjs/"
],
"license": "MIT",
diff --git a/packages/use-sync-external-store/shim/index.js b/packages/use-sync-external-store/shim/index.js
new file mode 100644
index 0000000000000..b8fa72b48e775
--- /dev/null
+++ b/packages/use-sync-external-store/shim/index.js
@@ -0,0 +1,12 @@
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ * @flow
+ */
+
+'use strict';
+
+export {useSyncExternalStore} from 'use-sync-external-store/src/useSyncExternalStoreShim';
diff --git a/packages/use-sync-external-store/index.native.js b/packages/use-sync-external-store/shim/index.native.js
similarity index 70%
rename from packages/use-sync-external-store/index.native.js
rename to packages/use-sync-external-store/shim/index.native.js
index cac5c1c2bf710..b8fa72b48e775 100644
--- a/packages/use-sync-external-store/index.native.js
+++ b/packages/use-sync-external-store/shim/index.native.js
@@ -9,4 +9,4 @@
'use strict';
-export * from './src/useSyncExternalStoreClient';
+export {useSyncExternalStore} from 'use-sync-external-store/src/useSyncExternalStoreShim';
diff --git a/packages/use-sync-external-store/shim/with-selector/index.js b/packages/use-sync-external-store/shim/with-selector/index.js
new file mode 100644
index 0000000000000..e71d4dac6ac9d
--- /dev/null
+++ b/packages/use-sync-external-store/shim/with-selector/index.js
@@ -0,0 +1,12 @@
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ * @flow
+ */
+
+'use strict';
+
+export {useSyncExternalStoreWithSelector} from 'use-sync-external-store/src/useSyncExternalStoreWithSelector';
diff --git a/packages/use-sync-external-store/src/__tests__/useSyncExternalStoreNative-test.js b/packages/use-sync-external-store/src/__tests__/useSyncExternalStoreNative-test.js
index 0902e7554c450..caedd74f74470 100644
--- a/packages/use-sync-external-store/src/__tests__/useSyncExternalStoreNative-test.js
+++ b/packages/use-sync-external-store/src/__tests__/useSyncExternalStoreNative-test.js
@@ -15,7 +15,7 @@ let React;
let ReactNoop;
let Scheduler;
let useSyncExternalStore;
-let useSyncExternalStoreExtra;
+let useSyncExternalStoreWithSelector;
let act;
// This tests the userspace shim of `useSyncExternalStore` in a server-rendering
@@ -36,25 +36,40 @@ describe('useSyncExternalStore (userspace shim, server rendering)', () => {
startTransition: _,
// eslint-disable-next-line no-unused-vars
useSyncExternalStore: __,
- // eslint-disable-next-line no-unused-vars
- unstable_useSyncExternalStore: ___,
...otherExports
} = jest.requireActual('react');
return otherExports;
});
- jest.mock('use-sync-external-store', () =>
- jest.requireActual('use-sync-external-store/index.native'),
+ jest.mock('use-sync-external-store/shim', () =>
+ jest.requireActual('use-sync-external-store/shim/index.native'),
);
React = require('react');
ReactNoop = require('react-noop-renderer');
Scheduler = require('scheduler');
act = require('jest-react').act;
- useSyncExternalStore = require('use-sync-external-store')
+
+ if (gate(flags => flags.source)) {
+ // The `shim/with-selector` module composes the main
+ // `use-sync-external-store` entrypoint. In the compiled artifacts, this
+ // is resolved to the `shim` implementation by our build config, but when
+ // running the tests against the source files, we need to tell Jest how to
+ // resolve it. Because this is a source module, this mock has no affect on
+ // the build tests.
+ jest.mock('use-sync-external-store/src/useSyncExternalStore', () =>
+ jest.requireActual('use-sync-external-store/shim'),
+ );
+ jest.mock('use-sync-external-store/src/isServerEnvironment', () =>
+ jest.requireActual(
+ 'use-sync-external-store/src/forks/isServerEnvironment.native',
+ ),
+ );
+ }
+ useSyncExternalStore = require('use-sync-external-store/shim')
.useSyncExternalStore;
- useSyncExternalStoreExtra = require('use-sync-external-store/extra')
- .useSyncExternalStoreExtra;
+ useSyncExternalStoreWithSelector = require('use-sync-external-store/shim/with-selector')
+ .useSyncExternalStoreWithSelector;
});
function Text({text}) {
@@ -105,32 +120,12 @@ describe('useSyncExternalStore (userspace shim, server rendering)', () => {
expect(root).toMatchRenderedOutput('client');
});
- test('native version', async () => {
- const store = createExternalStore('client');
-
- function App() {
- const text = useSyncExternalStore(
- store.subscribe,
- store.getState,
- () => 'server',
- );
- return ;
- }
-
- const root = ReactNoop.createRoot();
- await act(() => {
- root.render();
- });
- expect(Scheduler).toHaveYielded(['client']);
- expect(root).toMatchRenderedOutput('client');
- });
-
// @gate !(enableUseRefAccessWarning && __DEV__)
test('Using isEqual to bailout', async () => {
const store = createExternalStore({a: 0, b: 0});
function A() {
- const {a} = useSyncExternalStoreExtra(
+ const {a} = useSyncExternalStoreWithSelector(
store.subscribe,
store.getState,
null,
@@ -140,7 +135,7 @@ describe('useSyncExternalStore (userspace shim, server rendering)', () => {
return ;
}
function B() {
- const {b} = useSyncExternalStoreExtra(
+ const {b} = useSyncExternalStoreWithSelector(
store.subscribe,
store.getState,
null,
diff --git a/packages/use-sync-external-store/src/__tests__/useSyncExternalStoreShared-test.js b/packages/use-sync-external-store/src/__tests__/useSyncExternalStoreShared-test.js
index ab8143f24034c..5af9e9767ab0c 100644
--- a/packages/use-sync-external-store/src/__tests__/useSyncExternalStoreShared-test.js
+++ b/packages/use-sync-external-store/src/__tests__/useSyncExternalStoreShared-test.js
@@ -10,7 +10,7 @@
'use strict';
let useSyncExternalStore;
-let useSyncExternalStoreExtra;
+let useSyncExternalStoreWithSelector;
let React;
let ReactDOM;
let Scheduler;
@@ -25,11 +25,7 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => {
beforeEach(() => {
jest.resetModules();
- // Remove the built-in API from the React exports to force the package to
- // use the shim.
- if (!gate(flags => flags.supportsNativeUseSyncExternalStore)) {
- // and the non-variant tests for the shim.
- //
+ if (gate(flags => flags.enableUseSyncExternalStoreShim)) {
// Remove useSyncExternalStore from the React imports so that we use the
// shim instead. Also removing startTransition, since we use that to
// detect outdated 18 alphas that don't yet include useSyncExternalStore.
@@ -42,8 +38,6 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => {
startTransition: _,
// eslint-disable-next-line no-unused-vars
useSyncExternalStore: __,
- // eslint-disable-next-line no-unused-vars
- unstable_useSyncExternalStore: ___,
...otherExports
} = jest.requireActual('react');
return otherExports;
@@ -64,10 +58,21 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => {
// in both concurrent and legacy mode, I'm adding batching here.
act = cb => internalAct(() => ReactDOM.unstable_batchedUpdates(cb));
- useSyncExternalStore = require('use-sync-external-store')
+ if (gate(flags => flags.source)) {
+ // The `shim/with-selector` module composes the main
+ // `use-sync-external-store` entrypoint. In the compiled artifacts, this
+ // is resolved to the `shim` implementation by our build config, but when
+ // running the tests against the source files, we need to tell Jest how to
+ // resolve it. Because this is a source module, this mock has no affect on
+ // the build tests.
+ jest.mock('use-sync-external-store/src/useSyncExternalStore', () =>
+ jest.requireActual('use-sync-external-store/shim'),
+ );
+ }
+ useSyncExternalStore = require('use-sync-external-store/shim')
.useSyncExternalStore;
- useSyncExternalStoreExtra = require('use-sync-external-store/extra')
- .useSyncExternalStoreExtra;
+ useSyncExternalStoreWithSelector = require('use-sync-external-store/shim/with-selector')
+ .useSyncExternalStoreWithSelector;
});
function Text({text}) {
@@ -78,7 +83,7 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => {
function createRoot(container) {
// This wrapper function exists so we can test both legacy roots and
// concurrent roots.
- if (gate(flags => flags.supportsNativeUseSyncExternalStore)) {
+ if (gate(flags => !flags.enableUseSyncExternalStoreShim)) {
// The native implementation only exists in 18+, so we test using
// concurrent mode. To test the legacy root behavior in the native
// implementation (which is supported in the sense that it needs to have
@@ -265,7 +270,7 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => {
// In React 18, you can't observe in between a sync render and its
// passive effects, so this is only relevant to legacy roots
- // @gate !supportsNativeUseSyncExternalStore
+ // @gate enableUseSyncExternalStoreShim
test(
"compares to current state before bailing out, even when there's a " +
'mutation in between the sync and passive effects',
@@ -547,7 +552,7 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => {
await act(() => {
store.set({value: 1, throwInGetSnapshot: true, throwInIsEqual: false});
});
- if (gate(flags => flags.supportsNativeUseSyncExternalStore)) {
+ if (gate(flags => !flags.enableUseSyncExternalStoreShim)) {
expect(Scheduler).toHaveYielded([
'Error in getSnapshot',
// In a concurrent root, React renders a second time to attempt to
@@ -595,7 +600,7 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => {
function App() {
Scheduler.unstable_yieldValue('App');
- const a = useSyncExternalStoreExtra(
+ const a = useSyncExternalStoreWithSelector(
store.subscribe,
store.getState,
null,
@@ -632,7 +637,7 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => {
const store = createExternalStore({a: 0, b: 0});
function A() {
- const {a} = useSyncExternalStoreExtra(
+ const {a} = useSyncExternalStoreWithSelector(
store.subscribe,
store.getState,
null,
@@ -642,7 +647,7 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => {
return ;
}
function B() {
- const {b} = useSyncExternalStoreExtra(
+ const {b} = useSyncExternalStoreWithSelector(
store.subscribe,
store.getState,
null,
@@ -711,7 +716,7 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => {
container.innerHTML = '
server
';
const serverRenderedDiv = container.getElementsByTagName('div')[0];
- if (gate(flags => flags.supportsNativeUseSyncExternalStore)) {
+ if (gate(flags => !flags.enableUseSyncExternalStoreShim)) {
act(() => {
ReactDOM.hydrateRoot(container, );
});
@@ -774,7 +779,7 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => {
Scheduler.unstable_yieldValue('Inline selector');
return [...state.items, 'C'];
};
- const items = useSyncExternalStoreExtra(
+ const items = useSyncExternalStoreWithSelector(
store.subscribe,
store.getState,
null,
@@ -842,7 +847,7 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => {
const selector = state => state.a.toUpperCase();
function App() {
- const a = useSyncExternalStoreExtra(
+ const a = useSyncExternalStoreWithSelector(
store.subscribe,
store.getState,
null,
@@ -877,7 +882,7 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => {
const isEqual = (left, right) => left.a.trim() === right.a.trim();
function App() {
- const a = useSyncExternalStoreExtra(
+ const a = useSyncExternalStoreWithSelector(
store.subscribe,
store.getState,
null,
diff --git a/packages/use-sync-external-store/src/__tests__/useSyncExternalStoreShimServer-test.js b/packages/use-sync-external-store/src/__tests__/useSyncExternalStoreShimServer-test.js
index b46e028724061..016240a4d3f2f 100644
--- a/packages/use-sync-external-store/src/__tests__/useSyncExternalStoreShimServer-test.js
+++ b/packages/use-sync-external-store/src/__tests__/useSyncExternalStoreShimServer-test.js
@@ -35,8 +35,6 @@ describe('useSyncExternalStore (userspace shim, server rendering)', () => {
startTransition: _,
// eslint-disable-next-line no-unused-vars
useSyncExternalStore: __,
- // eslint-disable-next-line no-unused-vars
- unstable_useSyncExternalStore: ___,
...otherExports
} = jest.requireActual('react');
return otherExports;
@@ -47,7 +45,7 @@ describe('useSyncExternalStore (userspace shim, server rendering)', () => {
ReactDOMServer = require('react-dom/server');
Scheduler = require('scheduler');
- useSyncExternalStore = require('use-sync-external-store')
+ useSyncExternalStore = require('use-sync-external-store/shim')
.useSyncExternalStore;
});
diff --git a/packages/use-sync-external-store/extra.js b/packages/use-sync-external-store/src/forks/isServerEnvironment.native.js
similarity index 75%
rename from packages/use-sync-external-store/extra.js
rename to packages/use-sync-external-store/src/forks/isServerEnvironment.native.js
index 90d48eb4641b6..2bd359085541d 100644
--- a/packages/use-sync-external-store/extra.js
+++ b/packages/use-sync-external-store/src/forks/isServerEnvironment.native.js
@@ -7,6 +7,4 @@
* @flow
*/
-'use strict';
-
-export * from './src/useSyncExternalStoreExtra';
+export const isServerEnvironment = false;
diff --git a/packages/use-sync-external-store/src/forks/useSyncExternalStore.forward-to-built-in.js b/packages/use-sync-external-store/src/forks/useSyncExternalStore.forward-to-built-in.js
new file mode 100644
index 0000000000000..049f0c887e278
--- /dev/null
+++ b/packages/use-sync-external-store/src/forks/useSyncExternalStore.forward-to-built-in.js
@@ -0,0 +1,16 @@
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ * @flow
+ */
+
+'use strict';
+
+// Intentionally not using named imports because Rollup uses dynamic
+// dispatch for CommonJS interop named imports.
+import * as React from 'react';
+
+export const useSyncExternalStore = React.useSyncExternalStore;
diff --git a/packages/use-sync-external-store/src/forks/useSyncExternalStore.forward-to-shim.js b/packages/use-sync-external-store/src/forks/useSyncExternalStore.forward-to-shim.js
new file mode 100644
index 0000000000000..54425c3ae0e2d
--- /dev/null
+++ b/packages/use-sync-external-store/src/forks/useSyncExternalStore.forward-to-shim.js
@@ -0,0 +1,16 @@
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ * @flow
+ */
+
+'use strict';
+
+// Intentionally not using named imports because Rollup uses dynamic
+// dispatch for CommonJS interop named imports.
+import * as shim from 'use-sync-external-store/shim';
+
+export const useSyncExternalStore = shim.useSyncExternalStore;
diff --git a/packages/use-sync-external-store/src/isServerEnvironment.js b/packages/use-sync-external-store/src/isServerEnvironment.js
new file mode 100644
index 0000000000000..30adcb934731f
--- /dev/null
+++ b/packages/use-sync-external-store/src/isServerEnvironment.js
@@ -0,0 +1,12 @@
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ * @flow
+ */
+
+import {canUseDOM} from 'shared/ExecutionEnvironment';
+
+export const isServerEnvironment = !canUseDOM;
diff --git a/packages/use-sync-external-store/src/useSyncExternalStore.js b/packages/use-sync-external-store/src/useSyncExternalStore.js
index c152287174269..f1bb95556d2bb 100644
--- a/packages/use-sync-external-store/src/useSyncExternalStore.js
+++ b/packages/use-sync-external-store/src/useSyncExternalStore.js
@@ -7,16 +7,24 @@
* @flow
*/
-import {canUseDOM} from 'shared/ExecutionEnvironment';
-import {useSyncExternalStore as client} from './useSyncExternalStoreClient';
-import {useSyncExternalStore as server} from './useSyncExternalStoreServer';
+'use strict';
+
+// Intentionally not using named imports because Rollup uses dynamic
+// dispatch for CommonJS interop named imports.
import * as React from 'react';
-const {unstable_useSyncExternalStore: builtInAPI} = React;
+export const useSyncExternalStore = React.useSyncExternalStore;
-export const useSyncExternalStore =
- builtInAPI !== undefined
- ? ((builtInAPI: any): typeof client)
- : canUseDOM
- ? client
- : server;
+if (__DEV__) {
+ console.error(
+ "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+.' +
+ '\n\n' +
+ 'If you wish to support React 16 and 17, import from ' +
+ "'use-sync-external-store/shim' instead. It will fall back to a shimmed" +
+ 'implementation when the native one is not available.' +
+ '\n\n' +
+ "If you only support React 18+, you can import directly from 'react'.",
+ );
+}
diff --git a/packages/use-sync-external-store/src/useSyncExternalStoreShim.js b/packages/use-sync-external-store/src/useSyncExternalStoreShim.js
new file mode 100644
index 0000000000000..82d944de1dd1d
--- /dev/null
+++ b/packages/use-sync-external-store/src/useSyncExternalStoreShim.js
@@ -0,0 +1,18 @@
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ * @flow
+ */
+
+import {useSyncExternalStore as client} from './useSyncExternalStoreShimClient';
+import {useSyncExternalStore as server} from './useSyncExternalStoreShimServer';
+import {isServerEnvironment} from './isServerEnvironment';
+import {useSyncExternalStore as builtInAPI} from 'react';
+
+const shim = isServerEnvironment ? server : client;
+
+export const useSyncExternalStore =
+ builtInAPI !== undefined ? ((builtInAPI: any): typeof shim) : shim;
diff --git a/packages/use-sync-external-store/src/useSyncExternalStoreClient.js b/packages/use-sync-external-store/src/useSyncExternalStoreShimClient.js
similarity index 95%
rename from packages/use-sync-external-store/src/useSyncExternalStoreClient.js
rename to packages/use-sync-external-store/src/useSyncExternalStoreShimClient.js
index dc42169c399d6..8578b3d3fd1c7 100644
--- a/packages/use-sync-external-store/src/useSyncExternalStoreClient.js
+++ b/packages/use-sync-external-store/src/useSyncExternalStoreShimClient.js
@@ -30,10 +30,10 @@ let didWarnUncachedGetSnapshot = false;
export function useSyncExternalStore(
subscribe: (() => void) => () => void,
getSnapshot: () => T,
- // Note: The client shim does not use getServerSnapshot, because pre-18
- // versions of React do not expose a way to check if we're hydrating. So
- // users of the shim will need to track that themselves and return the
- // correct value from `getSnapshot`.
+ // Note: The shim does not use getServerSnapshot, because pre-18 versions of
+ // React do not expose a way to check if we're hydrating. So users of the shim
+ // will need to track that themselves and return the correct value
+ // from `getSnapshot`.
getServerSnapshot?: () => T,
): T {
if (__DEV__) {
diff --git a/packages/use-sync-external-store/src/useSyncExternalStoreServer.js b/packages/use-sync-external-store/src/useSyncExternalStoreShimServer.js
similarity index 59%
rename from packages/use-sync-external-store/src/useSyncExternalStoreServer.js
rename to packages/use-sync-external-store/src/useSyncExternalStoreShimServer.js
index 52903dd4aca89..4f2432718c365 100644
--- a/packages/use-sync-external-store/src/useSyncExternalStoreServer.js
+++ b/packages/use-sync-external-store/src/useSyncExternalStoreShimServer.js
@@ -12,5 +12,9 @@ export function useSyncExternalStore(
getSnapshot: () => T,
getServerSnapshot?: () => T,
): T {
+ // Note: The shim does not use getServerSnapshot, because pre-18 versions of
+ // React do not expose a way to check if we're hydrating. So users of the shim
+ // will need to track that themselves and return the correct value
+ // from `getSnapshot`.
return getSnapshot();
}
diff --git a/packages/use-sync-external-store/src/useSyncExternalStoreExtra.js b/packages/use-sync-external-store/src/useSyncExternalStoreWithSelector.js
similarity index 95%
rename from packages/use-sync-external-store/src/useSyncExternalStoreExtra.js
rename to packages/use-sync-external-store/src/useSyncExternalStoreWithSelector.js
index aa4957b534753..c7012e615ecd1 100644
--- a/packages/use-sync-external-store/src/useSyncExternalStoreExtra.js
+++ b/packages/use-sync-external-store/src/useSyncExternalStoreWithSelector.js
@@ -9,14 +9,14 @@
import * as React from 'react';
import is from 'shared/objectIs';
-import {useSyncExternalStore} from 'use-sync-external-store';
+import {useSyncExternalStore} from 'use-sync-external-store/src/useSyncExternalStore';
-// Intentionally not using named imports because Rollup uses dynamic
-// dispatch for CommonJS interop named imports.
+// Intentionally not using named imports because Rollup uses dynamic dispatch
+// for CommonJS interop.
const {useRef, useEffect, useMemo, useDebugValue} = React;
// Same as useSyncExternalStore, but supports selector and isEqual arguments.
-export function useSyncExternalStoreExtra(
+export function useSyncExternalStoreWithSelector(
subscribe: (() => void) => () => void,
getSnapshot: () => Snapshot,
getServerSnapshot: void | null | (() => Snapshot),
diff --git a/packages/use-sync-external-store/with-selector.js b/packages/use-sync-external-store/with-selector.js
new file mode 100644
index 0000000000000..e71d4dac6ac9d
--- /dev/null
+++ b/packages/use-sync-external-store/with-selector.js
@@ -0,0 +1,12 @@
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ * @flow
+ */
+
+'use strict';
+
+export {useSyncExternalStoreWithSelector} from 'use-sync-external-store/src/useSyncExternalStoreWithSelector';
diff --git a/scripts/jest/TestFlags.js b/scripts/jest/TestFlags.js
index c1bd03e00b1a9..af83baf0f3ec9 100644
--- a/scripts/jest/TestFlags.js
+++ b/scripts/jest/TestFlags.js
@@ -84,9 +84,8 @@ function getTestFlags() {
source: !process.env.IS_BUILD,
www,
- // This isn't a flag, just a useful alias for tests. Remove once
- // useSyncExternalStore lands in the `next` channel.
- supportsNativeUseSyncExternalStore: __EXPERIMENTAL__ || www,
+ // This isn't a flag, just a useful alias for tests.
+ enableUseSyncExternalStoreShim: !__VARIANT__,
// If there's a naming conflict between scheduler and React feature flags, the
// React ones take precedence.
diff --git a/scripts/jest/config.build.js b/scripts/jest/config.build.js
index 69cc7d18c536c..5b04ab05df7cd 100644
--- a/scripts/jest/config.build.js
+++ b/scripts/jest/config.build.js
@@ -45,6 +45,13 @@ packages.forEach(name => {
] = `/build/${NODE_MODULES_DIR}/${name}/$1`;
});
+moduleNameMapper[
+ 'use-sync-external-store/shim/with-selector'
+] = `/build/${NODE_MODULES_DIR}/use-sync-external-store/shim/with-selector`;
+moduleNameMapper[
+ 'use-sync-external-store/shim/index.native'
+] = `/build/${NODE_MODULES_DIR}/use-sync-external-store/shim/index.native`;
+
module.exports = Object.assign({}, baseConfig, {
// Redirect imports to the compiled bundles
moduleNameMapper,
diff --git a/scripts/rollup/bundles.js b/scripts/rollup/bundles.js
index 40ae43ae7cbc0..8b12e8550e365 100644
--- a/scripts/rollup/bundles.js
+++ b/scripts/rollup/bundles.js
@@ -789,7 +789,7 @@ const bundles = [
externals: ['react'],
},
- /******* Shim for useSyncExternalStore *******/
+ /******* useSyncExternalStore *******/
{
bundleTypes: [NODE_DEV, NODE_PROD],
moduleType: ISOMORPHIC,
@@ -800,26 +800,48 @@ const bundles = [
externals: ['react'],
},
- /******* Shim for useSyncExternalStore (+ extra user-space features) *******/
+ /******* useSyncExternalStore (shim) *******/
{
bundleTypes: [NODE_DEV, NODE_PROD],
moduleType: ISOMORPHIC,
- entry: 'use-sync-external-store/extra',
- global: 'useSyncExternalStoreExtra',
- minifyWithProdErrorCodes: true,
+ entry: 'use-sync-external-store/shim',
+ global: 'useSyncExternalStore',
+ minifyWithProdErrorCodes: false,
wrapWithModuleBoundaries: true,
- externals: ['react', 'use-sync-external-store'],
+ externals: ['react'],
},
- /******* Shim for useSyncExternalStore ReactNative *******/
+ /******* useSyncExternalStore (shim, native) *******/
{
bundleTypes: [NODE_DEV, NODE_PROD],
moduleType: ISOMORPHIC,
- entry: 'use-sync-external-store/index.native',
- global: 'useSyncExternalStoreNative',
- minifyWithProdErrorCodes: true,
+ entry: 'use-sync-external-store/shim/index.native',
+ global: 'useSyncExternalStore',
+ minifyWithProdErrorCodes: false,
wrapWithModuleBoundaries: true,
- externals: ['react', 'ReactNativeInternalFeatureFlags'],
+ externals: ['react'],
+ },
+
+ /******* useSyncExternalStoreWithSelector *******/
+ {
+ bundleTypes: [NODE_DEV, NODE_PROD],
+ moduleType: ISOMORPHIC,
+ entry: 'use-sync-external-store/with-selector',
+ global: 'useSyncExternalStoreWithSelector',
+ minifyWithProdErrorCodes: false,
+ wrapWithModuleBoundaries: true,
+ externals: ['react'],
+ },
+
+ /******* useSyncExternalStoreWithSelector (shim) *******/
+ {
+ bundleTypes: [NODE_DEV, NODE_PROD],
+ moduleType: ISOMORPHIC,
+ entry: 'use-sync-external-store/shim/with-selector',
+ global: 'useSyncExternalStoreWithSelector',
+ minifyWithProdErrorCodes: false,
+ wrapWithModuleBoundaries: true,
+ externals: ['react', 'use-sync-external-store/shim'],
},
/******* React Scheduler (experimental) *******/
diff --git a/scripts/rollup/forks.js b/scripts/rollup/forks.js
index 84211da5c5125..b2234fa42a010 100644
--- a/scripts/rollup/forks.js
+++ b/scripts/rollup/forks.js
@@ -481,6 +481,24 @@ const forks = Object.freeze({
return null;
}
},
+
+ 'use-sync-external-store/src/useSyncExternalStore': (bundleType, entry) => {
+ if (entry.startsWith('use-sync-external-store/shim')) {
+ return 'use-sync-external-store/src/forks/useSyncExternalStore.forward-to-shim';
+ }
+ if (entry !== 'use-sync-external-store') {
+ // Internal modules that aren't shims should use the native API from the
+ // react package.
+ return 'use-sync-external-store/src/forks/useSyncExternalStore.forward-to-built-in';
+ }
+ return null;
+ },
+
+ 'use-sync-external-store/src/isServerEnvironment': (bundleType, entry) => {
+ if (entry.endsWith('.native')) {
+ return 'use-sync-external-store/src/forks/isServerEnvironment.native';
+ }
+ },
});
module.exports = forks;
diff --git a/scripts/shared/pathsByLanguageVersion.js b/scripts/shared/pathsByLanguageVersion.js
index af6963f78949e..8c60a9c074a5a 100644
--- a/scripts/shared/pathsByLanguageVersion.js
+++ b/scripts/shared/pathsByLanguageVersion.js
@@ -11,6 +11,8 @@ const esNextPaths = [
// Internal forwarding modules
'packages/*/*.js',
'packages/*/esm/*.js',
+ 'packages/use-sync-external-store/shim/**/*.js',
+ 'packages/use-sync-external-store/with-selector/**/*.js',
// Source files
'packages/*/src/**/*.js',
'packages/dom-event-testing-library/**/*.js',