Skip to content

Commit 6bce035

Browse files
authored
Upgrade useSyncExternalStore to alpha channel (#22662)
* Move useSyncExternalStore shim to a nested entrypoint Also renames `useSyncExternalStoreExtra` to `useSyncExternalStoreWithSelector`. - 'use-sync-external-store/shim' -> A shim for `useSyncExternalStore` that works in React 16 and 17 (any release that supports hooks). The module will first check if the built-in React API exists, before falling back to the shim. - 'use-sync-external-store/with-selector' -> An extended version of `useSyncExternalStore` that also supports `selector` and `isEqual` options. It does _not_ shim `use-sync-external-store`; it composes the built-in React API. **Use this if you only support 18+.** - 'use-sync-external-store/shim/with-selector' -> Same API, but it composes `use-sync-external-store/shim` instead. **Use this for compatibility with 16 and 17.** - 'use-sync-external-store' -> Re-exports React's built-in API. Not meant to be used. It will warn and direct users to either the shim or the built-in API. * Upgrade useSyncExternalStore to alpha channel
1 parent 7034408 commit 6bce035

38 files changed

+298
-124
lines changed

ReactVersions.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ const stablePackages = {
3636
'react-refresh': '0.11.0',
3737
'react-test-renderer': ReactVersion,
3838
'use-subscription': '1.6.0',
39+
'use-sync-external-store': '1.0.0',
3940
scheduler: '0.21.0',
4041
};
4142

@@ -47,7 +48,6 @@ const experimentalPackages = [
4748
'react-fs',
4849
'react-pg',
4950
'react-server-dom-webpack',
50-
'use-sync-external-store',
5151
];
5252

5353
module.exports = {

packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js

+1-2
Original file line numberDiff line numberDiff line change
@@ -1055,9 +1055,8 @@ describe('ReactHooksInspectionIntegration', () => {
10551055
]);
10561056
});
10571057

1058-
// @gate experimental || www
10591058
it('should support composite useSyncExternalStore hook', () => {
1060-
const useSyncExternalStore = React.unstable_useSyncExternalStore;
1059+
const useSyncExternalStore = React.useSyncExternalStore;
10611060
function Foo() {
10621061
const value = useSyncExternalStore(
10631062
() => () => {},

packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js

+17-9
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ let ReactDOMFizzServer;
1818
let Suspense;
1919
let SuspenseList;
2020
let useSyncExternalStore;
21-
let useSyncExternalStoreExtra;
21+
let useSyncExternalStoreWithSelector;
2222
let PropTypes;
2323
let textCache;
2424
let window;
@@ -43,11 +43,23 @@ describe('ReactDOMFizzServer', () => {
4343
Stream = require('stream');
4444
Suspense = React.Suspense;
4545
SuspenseList = React.SuspenseList;
46-
useSyncExternalStore = React.unstable_useSyncExternalStore;
47-
useSyncExternalStoreExtra = require('use-sync-external-store/extra')
48-
.useSyncExternalStoreExtra;
46+
4947
PropTypes = require('prop-types');
5048

49+
if (gate(flags => flags.source)) {
50+
// The `with-selector` module composes the main `use-sync-external-store`
51+
// entrypoint. In the compiled artifacts, this is resolved to the `shim`
52+
// implementation by our build config, but when running the tests against
53+
// the source files, we need to tell Jest how to resolve it. Because this
54+
// is a source module, this mock has no affect on the build tests.
55+
jest.mock('use-sync-external-store/src/useSyncExternalStore', () =>
56+
jest.requireActual('react'),
57+
);
58+
}
59+
useSyncExternalStore = React.useSyncExternalStore;
60+
useSyncExternalStoreWithSelector = require('use-sync-external-store/with-selector')
61+
.useSyncExternalStoreWithSelector;
62+
5163
textCache = new Map();
5264

5365
// Test Environment
@@ -1663,7 +1675,6 @@ describe('ReactDOMFizzServer', () => {
16631675
);
16641676
});
16651677

1666-
// @gate supportsNativeUseSyncExternalStore
16671678
// @gate experimental
16681679
it('calls getServerSnapshot instead of getSnapshot', async () => {
16691680
const ref = React.createRef();
@@ -1734,7 +1745,6 @@ describe('ReactDOMFizzServer', () => {
17341745

17351746
// The selector implementation uses the lazy ref initialization pattern
17361747
// @gate !(enableUseRefAccessWarning && __DEV__)
1737-
// @gate supportsNativeUseSyncExternalStore
17381748
// @gate experimental
17391749
it('calls getServerSnapshot instead of getSnapshot (with selector and isEqual)', async () => {
17401750
// Same as previous test, but with a selector that returns a complex object
@@ -1767,7 +1777,7 @@ describe('ReactDOMFizzServer', () => {
17671777
}
17681778

17691779
function App() {
1770-
const {env} = useSyncExternalStoreExtra(
1780+
const {env} = useSyncExternalStoreWithSelector(
17711781
subscribe,
17721782
getClientSnapshot,
17731783
getServerSnapshot,
@@ -1815,7 +1825,6 @@ describe('ReactDOMFizzServer', () => {
18151825
expect(ref.current).toEqual(serverRenderedDiv);
18161826
});
18171827

1818-
// @gate supportsNativeUseSyncExternalStore
18191828
// @gate experimental
18201829
it(
18211830
'errors during hydration force a client render at the nearest Suspense ' +
@@ -1964,7 +1973,6 @@ describe('ReactDOMFizzServer', () => {
19641973
},
19651974
);
19661975

1967-
// @gate supportsNativeUseSyncExternalStore
19681976
// @gate experimental
19691977
it(
19701978
'errors during hydration force a client render at the nearest Suspense ' +

packages/react-reconciler/src/__tests__/useSyncExternalStore-test.js

+1-2
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ describe('useSyncExternalStore', () => {
3636
useImperativeHandle = React.useImperativeHandle;
3737
forwardRef = React.forwardRef;
3838
useRef = React.useRef;
39-
useSyncExternalStore = React.unstable_useSyncExternalStore;
39+
useSyncExternalStore = React.useSyncExternalStore;
4040
startTransition = React.startTransition;
4141

4242
act = require('jest-react').act;
@@ -70,7 +70,6 @@ describe('useSyncExternalStore', () => {
7070
};
7171
}
7272

73-
// @gate supportsNativeUseSyncExternalStore
7473
test(
7574
'detects interleaved mutations during a concurrent read before ' +
7675
'layout effects fire',

packages/react/index.classic.fb.js

-1
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,6 @@ export {
5454
useMutableSource,
5555
useMutableSource as unstable_useMutableSource,
5656
useSyncExternalStore,
57-
useSyncExternalStore as unstable_useSyncExternalStore,
5857
useReducer,
5958
useRef,
6059
useState,

packages/react/index.experimental.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ export {
4747
useLayoutEffect,
4848
useMemo,
4949
useMutableSource as unstable_useMutableSource,
50-
useSyncExternalStore as unstable_useSyncExternalStore,
50+
useSyncExternalStore,
5151
useReducer,
5252
useRef,
5353
useState,

packages/react/index.js

-1
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,6 @@ export {
7373
useMemo,
7474
useMutableSource,
7575
useSyncExternalStore,
76-
useSyncExternalStore as unstable_useSyncExternalStore,
7776
useReducer,
7877
useRef,
7978
useState,

packages/react/index.modern.fb.js

-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,6 @@ export {
5353
useMutableSource,
5454
useMutableSource as unstable_useMutableSource,
5555
useSyncExternalStore,
56-
useSyncExternalStore as unstable_useSyncExternalStore,
5756
useReducer,
5857
useRef,
5958
useState,

packages/react/index.stable.js

+1
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ export {
4040
useLayoutEffect,
4141
useMemo,
4242
useMutableSource as unstable_useMutableSource,
43+
useSyncExternalStore,
4344
useReducer,
4445
useRef,
4546
useState,

packages/use-sync-external-store/index.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,4 @@
99

1010
'use strict';
1111

12-
export * from './src/useSyncExternalStore';
12+
export {useSyncExternalStore} from './src/useSyncExternalStore';

packages/use-sync-external-store/npm/extra.js

-7
This file was deleted.

packages/use-sync-external-store/npm/index.native.js

-7
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
'use strict';
2+
3+
if (process.env.NODE_ENV === 'production') {
4+
module.exports = require('../cjs/use-sync-external-store-shim.production.min.js');
5+
} else {
6+
module.exports = require('../cjs/use-sync-external-store-shim.development.js');
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
'use strict';
2+
3+
if (process.env.NODE_ENV === 'production') {
4+
module.exports = require('../cjs/use-sync-external-store-shim.native.production.min.js');
5+
} else {
6+
module.exports = require('../cjs/use-sync-external-store-shim.native.development.js');
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
'use strict';
2+
3+
if (process.env.NODE_ENV === 'production') {
4+
module.exports = require('../cjs/use-sync-external-store-shim/with-selector.production.min.js');
5+
} else {
6+
module.exports = require('../cjs/use-sync-external-store-shim/with-selector.development.js');
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
'use strict';
2+
3+
if (process.env.NODE_ENV === 'production') {
4+
module.exports = require('./cjs/use-sync-external-store-with-selector.production.min.js');
5+
} else {
6+
module.exports = require('./cjs/use-sync-external-store-with-selector.development.js');
7+
}

packages/use-sync-external-store/package.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@
1212
"README.md",
1313
"build-info.json",
1414
"index.js",
15-
"extra.js",
1615
"index.native.js",
16+
"with-selector.js",
17+
"with-selector.native.js",
18+
"shim/",
1719
"cjs/"
1820
],
1921
"license": "MIT",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @flow
8+
*/
9+
10+
'use strict';
11+
12+
export {useSyncExternalStore} from 'use-sync-external-store/src/useSyncExternalStoreShim';

packages/use-sync-external-store/index.native.js renamed to packages/use-sync-external-store/shim/index.native.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,4 @@
99

1010
'use strict';
1111

12-
export * from './src/useSyncExternalStoreClient';
12+
export {useSyncExternalStore} from 'use-sync-external-store/src/useSyncExternalStoreShim';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @flow
8+
*/
9+
10+
'use strict';
11+
12+
export {useSyncExternalStoreWithSelector} from 'use-sync-external-store/src/useSyncExternalStoreWithSelector';

packages/use-sync-external-store/src/__tests__/useSyncExternalStoreNative-test.js

+25-30
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ let React;
1515
let ReactNoop;
1616
let Scheduler;
1717
let useSyncExternalStore;
18-
let useSyncExternalStoreExtra;
18+
let useSyncExternalStoreWithSelector;
1919
let act;
2020

2121
// This tests the userspace shim of `useSyncExternalStore` in a server-rendering
@@ -36,25 +36,40 @@ describe('useSyncExternalStore (userspace shim, server rendering)', () => {
3636
startTransition: _,
3737
// eslint-disable-next-line no-unused-vars
3838
useSyncExternalStore: __,
39-
// eslint-disable-next-line no-unused-vars
40-
unstable_useSyncExternalStore: ___,
4139
...otherExports
4240
} = jest.requireActual('react');
4341
return otherExports;
4442
});
4543

46-
jest.mock('use-sync-external-store', () =>
47-
jest.requireActual('use-sync-external-store/index.native'),
44+
jest.mock('use-sync-external-store/shim', () =>
45+
jest.requireActual('use-sync-external-store/shim/index.native'),
4846
);
4947

5048
React = require('react');
5149
ReactNoop = require('react-noop-renderer');
5250
Scheduler = require('scheduler');
5351
act = require('jest-react').act;
54-
useSyncExternalStore = require('use-sync-external-store')
52+
53+
if (gate(flags => flags.source)) {
54+
// The `shim/with-selector` module composes the main
55+
// `use-sync-external-store` entrypoint. In the compiled artifacts, this
56+
// is resolved to the `shim` implementation by our build config, but when
57+
// running the tests against the source files, we need to tell Jest how to
58+
// resolve it. Because this is a source module, this mock has no affect on
59+
// the build tests.
60+
jest.mock('use-sync-external-store/src/useSyncExternalStore', () =>
61+
jest.requireActual('use-sync-external-store/shim'),
62+
);
63+
jest.mock('use-sync-external-store/src/isServerEnvironment', () =>
64+
jest.requireActual(
65+
'use-sync-external-store/src/forks/isServerEnvironment.native',
66+
),
67+
);
68+
}
69+
useSyncExternalStore = require('use-sync-external-store/shim')
5570
.useSyncExternalStore;
56-
useSyncExternalStoreExtra = require('use-sync-external-store/extra')
57-
.useSyncExternalStoreExtra;
71+
useSyncExternalStoreWithSelector = require('use-sync-external-store/shim/with-selector')
72+
.useSyncExternalStoreWithSelector;
5873
});
5974

6075
function Text({text}) {
@@ -105,32 +120,12 @@ describe('useSyncExternalStore (userspace shim, server rendering)', () => {
105120
expect(root).toMatchRenderedOutput('client');
106121
});
107122

108-
test('native version', async () => {
109-
const store = createExternalStore('client');
110-
111-
function App() {
112-
const text = useSyncExternalStore(
113-
store.subscribe,
114-
store.getState,
115-
() => 'server',
116-
);
117-
return <Text text={text} />;
118-
}
119-
120-
const root = ReactNoop.createRoot();
121-
await act(() => {
122-
root.render(<App />);
123-
});
124-
expect(Scheduler).toHaveYielded(['client']);
125-
expect(root).toMatchRenderedOutput('client');
126-
});
127-
128123
// @gate !(enableUseRefAccessWarning && __DEV__)
129124
test('Using isEqual to bailout', async () => {
130125
const store = createExternalStore({a: 0, b: 0});
131126

132127
function A() {
133-
const {a} = useSyncExternalStoreExtra(
128+
const {a} = useSyncExternalStoreWithSelector(
134129
store.subscribe,
135130
store.getState,
136131
null,
@@ -140,7 +135,7 @@ describe('useSyncExternalStore (userspace shim, server rendering)', () => {
140135
return <Text text={'A' + a} />;
141136
}
142137
function B() {
143-
const {b} = useSyncExternalStoreExtra(
138+
const {b} = useSyncExternalStoreWithSelector(
144139
store.subscribe,
145140
store.getState,
146141
null,

0 commit comments

Comments
 (0)