Skip to content

Commit

Permalink
Upgrade useSyncExternalStore to alpha channel (#22662)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
acdlite authored Oct 31, 2021
1 parent 7034408 commit 6bce035
Show file tree
Hide file tree
Showing 38 changed files with 298 additions and 124 deletions.
2 changes: 1 addition & 1 deletion ReactVersions.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
};

Expand All @@ -47,7 +48,6 @@ const experimentalPackages = [
'react-fs',
'react-pg',
'react-server-dom-webpack',
'use-sync-external-store',
];

module.exports = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
() => () => {},
Expand Down
26 changes: 17 additions & 9 deletions packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ let ReactDOMFizzServer;
let Suspense;
let SuspenseList;
let useSyncExternalStore;
let useSyncExternalStoreExtra;
let useSyncExternalStoreWithSelector;
let PropTypes;
let textCache;
let window;
Expand All @@ -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
Expand Down Expand Up @@ -1663,7 +1675,6 @@ describe('ReactDOMFizzServer', () => {
);
});

// @gate supportsNativeUseSyncExternalStore
// @gate experimental
it('calls getServerSnapshot instead of getSnapshot', async () => {
const ref = React.createRef();
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -1767,7 +1777,7 @@ describe('ReactDOMFizzServer', () => {
}

function App() {
const {env} = useSyncExternalStoreExtra(
const {env} = useSyncExternalStoreWithSelector(
subscribe,
getClientSnapshot,
getServerSnapshot,
Expand Down Expand Up @@ -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 ' +
Expand Down Expand Up @@ -1964,7 +1973,6 @@ describe('ReactDOMFizzServer', () => {
},
);

// @gate supportsNativeUseSyncExternalStore
// @gate experimental
it(
'errors during hydration force a client render at the nearest Suspense ' +
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -70,7 +70,6 @@ describe('useSyncExternalStore', () => {
};
}

// @gate supportsNativeUseSyncExternalStore
test(
'detects interleaved mutations during a concurrent read before ' +
'layout effects fire',
Expand Down
1 change: 0 additions & 1 deletion packages/react/index.classic.fb.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,6 @@ export {
useMutableSource,
useMutableSource as unstable_useMutableSource,
useSyncExternalStore,
useSyncExternalStore as unstable_useSyncExternalStore,
useReducer,
useRef,
useState,
Expand Down
2 changes: 1 addition & 1 deletion packages/react/index.experimental.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export {
useLayoutEffect,
useMemo,
useMutableSource as unstable_useMutableSource,
useSyncExternalStore as unstable_useSyncExternalStore,
useSyncExternalStore,
useReducer,
useRef,
useState,
Expand Down
1 change: 0 additions & 1 deletion packages/react/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,6 @@ export {
useMemo,
useMutableSource,
useSyncExternalStore,
useSyncExternalStore as unstable_useSyncExternalStore,
useReducer,
useRef,
useState,
Expand Down
1 change: 0 additions & 1 deletion packages/react/index.modern.fb.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ export {
useMutableSource,
useMutableSource as unstable_useMutableSource,
useSyncExternalStore,
useSyncExternalStore as unstable_useSyncExternalStore,
useReducer,
useRef,
useState,
Expand Down
1 change: 1 addition & 0 deletions packages/react/index.stable.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export {
useLayoutEffect,
useMemo,
useMutableSource as unstable_useMutableSource,
useSyncExternalStore,
useReducer,
useRef,
useState,
Expand Down
2 changes: 1 addition & 1 deletion packages/use-sync-external-store/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@

'use strict';

export * from './src/useSyncExternalStore';
export {useSyncExternalStore} from './src/useSyncExternalStore';
7 changes: 0 additions & 7 deletions packages/use-sync-external-store/npm/extra.js

This file was deleted.

7 changes: 0 additions & 7 deletions packages/use-sync-external-store/npm/index.native.js

This file was deleted.

7 changes: 7 additions & 0 deletions packages/use-sync-external-store/npm/shim/index.js
Original file line number Diff line number Diff line change
@@ -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');
}
7 changes: 7 additions & 0 deletions packages/use-sync-external-store/npm/shim/index.native.js
Original file line number Diff line number Diff line change
@@ -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');
}
7 changes: 7 additions & 0 deletions packages/use-sync-external-store/npm/shim/with-selector.js
Original file line number Diff line number Diff line change
@@ -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');
}
7 changes: 7 additions & 0 deletions packages/use-sync-external-store/npm/with-selector.js
Original file line number Diff line number Diff line change
@@ -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');
}
4 changes: 3 additions & 1 deletion packages/use-sync-external-store/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
12 changes: 12 additions & 0 deletions packages/use-sync-external-store/shim/index.js
Original file line number Diff line number Diff line change
@@ -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';
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@

'use strict';

export * from './src/useSyncExternalStoreClient';
export {useSyncExternalStore} from 'use-sync-external-store/src/useSyncExternalStoreShim';
12 changes: 12 additions & 0 deletions packages/use-sync-external-store/shim/with-selector/index.js
Original file line number Diff line number Diff line change
@@ -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';
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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}) {
Expand Down Expand Up @@ -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 <Text text={text} />;
}

const root = ReactNoop.createRoot();
await act(() => {
root.render(<App />);
});
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,
Expand All @@ -140,7 +135,7 @@ describe('useSyncExternalStore (userspace shim, server rendering)', () => {
return <Text text={'A' + a} />;
}
function B() {
const {b} = useSyncExternalStoreExtra(
const {b} = useSyncExternalStoreWithSelector(
store.subscribe,
store.getState,
null,
Expand Down
Loading

0 comments on commit 6bce035

Please sign in to comment.