Add unstable prefix to experimental APIs (#18825)

We've been shipping unprefixed experimental APIs (like `createRoot` and
`useTransition`) to the Experimental release channel, with the rationale
that because these APIs do not appear in any stable release, we're free
to change or remove them later without breaking any downstream projects.

What we didn't consider is that downstream projects might be tempted to
use feature detection:

```js
const useTransition = React.useTransition || fallbackUseTransition;
```

This pattern assumes that the version of `useTransition` that exists in
the Experimental channel today has the same API contract as the final
`useTransition` API that we'll eventually ship to stable.

To discourage feature detection, I've added an `unstable_` prefix to
all of our unstable APIs.

The Facebook builds still have the unprefixed APIs, though. We will
continue to support those; if we make any breaking changes, we'll
migrate the internal callers like we usually do. To make testing easier,
I added the `unstable_`-prefixed APIs to the www builds, too. That way
our tests can always use the prefixed ones without gating on the
release channel.
This commit is contained in:
Andrew Clark
2020-05-04 22:25:41 -07:00
committed by GitHub
parent cd4a96035f
commit fe7163e73d
31 changed files with 284 additions and 218 deletions
+1 -1
View File
@@ -7,7 +7,7 @@
/* eslint-disable import/first */
import * as React from 'react';
import {block} from 'react';
import {unstable_block as block} from 'react';
// Server
+1 -1
View File
@@ -179,7 +179,7 @@ it("doesn't warn if you use nested acts from different renderers", () => {
if (__EXPERIMENTAL__) {
it('warns when using createRoot() + .render', () => {
const root = ReactDOM.createRoot(document.createElement('div'));
const root = ReactDOM.unstable_createRoot(document.createElement('div'));
expect(() => {
TestRenderer.act(() => {
root.render(<App />);
@@ -366,121 +366,123 @@ describe('ReactHooksInspectionIntegration', () => {
]);
});
if (__EXPERIMENTAL__) {
it('should support composite useTransition hook', () => {
function Foo(props) {
React.useTransition();
const memoizedValue = React.useMemo(() => 'hello', []);
return <div>{memoizedValue}</div>;
}
const renderer = ReactTestRenderer.create(<Foo />);
const childFiber = renderer.root.findByType(Foo)._currentFiber();
const tree = ReactDebugTools.inspectHooksOfFiber(childFiber);
expect(tree).toEqual([
{
id: 0,
isStateEditable: false,
name: 'Transition',
value: undefined,
subHooks: [],
},
{
id: 1,
isStateEditable: false,
name: 'Memo',
value: 'hello',
subHooks: [],
},
]);
});
// @gate experimental
it('should support composite useTransition hook', () => {
function Foo(props) {
React.unstable_useTransition();
const memoizedValue = React.useMemo(() => 'hello', []);
return <div>{memoizedValue}</div>;
}
const renderer = ReactTestRenderer.create(<Foo />);
const childFiber = renderer.root.findByType(Foo)._currentFiber();
const tree = ReactDebugTools.inspectHooksOfFiber(childFiber);
expect(tree).toEqual([
{
id: 0,
isStateEditable: false,
name: 'Transition',
value: undefined,
subHooks: [],
},
{
id: 1,
isStateEditable: false,
name: 'Memo',
value: 'hello',
subHooks: [],
},
]);
});
it('should support composite useDeferredValue hook', () => {
function Foo(props) {
React.useDeferredValue('abc', {
timeoutMs: 500,
});
const [state] = React.useState(() => 'hello', []);
return <div>{state}</div>;
}
const renderer = ReactTestRenderer.create(<Foo />);
const childFiber = renderer.root.findByType(Foo)._currentFiber();
const tree = ReactDebugTools.inspectHooksOfFiber(childFiber);
expect(tree).toEqual([
{
id: 0,
isStateEditable: false,
name: 'DeferredValue',
value: 'abc',
subHooks: [],
},
{
id: 1,
isStateEditable: true,
name: 'State',
value: 'hello',
subHooks: [],
},
]);
});
it('should support composite useOpaqueIdentifier hook', () => {
function Foo(props) {
const id = React.unstable_useOpaqueIdentifier();
const [state] = React.useState(() => 'hello', []);
return <div id={id}>{state}</div>;
}
const renderer = ReactTestRenderer.create(<Foo />);
const childFiber = renderer.root.findByType(Foo)._currentFiber();
const tree = ReactDebugTools.inspectHooksOfFiber(childFiber);
expect(tree.length).toEqual(2);
expect(tree[0].id).toEqual(0);
expect(tree[0].isStateEditable).toEqual(false);
expect(tree[0].name).toEqual('OpaqueIdentifier');
expect((tree[0].value + '').startsWith('c_')).toBe(true);
expect(tree[1]).toEqual({
// @gate experimental
it('should support composite useDeferredValue hook', () => {
function Foo(props) {
React.unstable_useDeferredValue('abc', {
timeoutMs: 500,
});
const [state] = React.useState(() => 'hello', []);
return <div>{state}</div>;
}
const renderer = ReactTestRenderer.create(<Foo />);
const childFiber = renderer.root.findByType(Foo)._currentFiber();
const tree = ReactDebugTools.inspectHooksOfFiber(childFiber);
expect(tree).toEqual([
{
id: 0,
isStateEditable: false,
name: 'DeferredValue',
value: 'abc',
subHooks: [],
},
{
id: 1,
isStateEditable: true,
name: 'State',
value: 'hello',
subHooks: [],
});
},
]);
});
// @gate experimental
it('should support composite useOpaqueIdentifier hook', () => {
function Foo(props) {
const id = React.unstable_useOpaqueIdentifier();
const [state] = React.useState(() => 'hello', []);
return <div id={id}>{state}</div>;
}
const renderer = ReactTestRenderer.create(<Foo />);
const childFiber = renderer.root.findByType(Foo)._currentFiber();
const tree = ReactDebugTools.inspectHooksOfFiber(childFiber);
expect(tree.length).toEqual(2);
expect(tree[0].id).toEqual(0);
expect(tree[0].isStateEditable).toEqual(false);
expect(tree[0].name).toEqual('OpaqueIdentifier');
expect((tree[0].value + '').startsWith('c_')).toBe(true);
expect(tree[1]).toEqual({
id: 1,
isStateEditable: true,
name: 'State',
value: 'hello',
subHooks: [],
});
});
it('should support composite useOpaqueIdentifier hook in concurrent mode', () => {
function Foo(props) {
const id = React.unstable_useOpaqueIdentifier();
const [state] = React.useState(() => 'hello', []);
return <div id={id}>{state}</div>;
}
// @gate experimental
it('should support composite useOpaqueIdentifier hook in concurrent mode', () => {
function Foo(props) {
const id = React.unstable_useOpaqueIdentifier();
const [state] = React.useState(() => 'hello', []);
return <div id={id}>{state}</div>;
}
const renderer = ReactTestRenderer.create(<Foo />, {
unstable_isConcurrent: true,
});
expect(Scheduler).toFlushWithoutYielding();
const childFiber = renderer.root.findByType(Foo)._currentFiber();
const tree = ReactDebugTools.inspectHooksOfFiber(childFiber);
expect(tree.length).toEqual(2);
expect(tree[0].id).toEqual(0);
expect(tree[0].isStateEditable).toEqual(false);
expect(tree[0].name).toEqual('OpaqueIdentifier');
expect((tree[0].value + '').startsWith('c_')).toBe(true);
expect(tree[1]).toEqual({
id: 1,
isStateEditable: true,
name: 'State',
value: 'hello',
subHooks: [],
});
const renderer = ReactTestRenderer.create(<Foo />, {
unstable_isConcurrent: true,
});
}
expect(Scheduler).toFlushWithoutYielding();
const childFiber = renderer.root.findByType(Foo)._currentFiber();
const tree = ReactDebugTools.inspectHooksOfFiber(childFiber);
expect(tree.length).toEqual(2);
expect(tree[0].id).toEqual(0);
expect(tree[0].isStateEditable).toEqual(false);
expect(tree[0].name).toEqual('OpaqueIdentifier');
expect((tree[0].value + '').startsWith('c_')).toBe(true);
expect(tree[1]).toEqual({
id: 1,
isStateEditable: true,
name: 'State',
value: 'hello',
subHooks: [],
});
});
describe('useDebugValue', () => {
it('should support inspectable values for multiple custom hooks', () => {
+3 -3
View File
@@ -352,18 +352,18 @@ describe('Store', () => {
};
const Wrapper = ({shouldSuspense}) => (
<React.Fragment>
<React.SuspenseList revealOrder="forwards" tail="collapsed">
<React.unstable_SuspenseList revealOrder="forwards" tail="collapsed">
<Component key="A" />
<React.Suspense fallback={<Loading />}>
{shouldSuspense ? <SuspendingComponent /> : <Component key="B" />}
</React.Suspense>
<Component key="C" />
</React.SuspenseList>
</React.unstable_SuspenseList>
</React.Fragment>
);
const container = document.createElement('div');
const root = ReactDOM.createRoot(container);
const root = ReactDOM.unstable_createRoot(container);
act(() => {
root.render(<Wrapper shouldSuspense={true} />);
});
@@ -63,7 +63,7 @@ describe('StoreStressConcurrent', () => {
// 1. Render a normal version of [a, b, c, d, e].
let container = document.createElement('div');
// $FlowFixMe
let root = ReactDOM.createRoot(container);
let root = ReactDOM.unstable_createRoot(container);
act(() => root.render(<Parent>{[a, b, c, d, e]}</Parent>));
expect(store).toMatchInlineSnapshot(
`
@@ -147,7 +147,7 @@ describe('StoreStressConcurrent', () => {
// Ensure fresh mount.
container = document.createElement('div');
// $FlowFixMe
root = ReactDOM.createRoot(container);
root = ReactDOM.unstable_createRoot(container);
// Verify mounting 'abcde'.
act(() => root.render(<Parent>{cases[i]}</Parent>));
@@ -177,7 +177,7 @@ describe('StoreStressConcurrent', () => {
// There'll be no unmounting until the very end.
container = document.createElement('div');
// $FlowFixMe
root = ReactDOM.createRoot(container);
root = ReactDOM.unstable_createRoot(container);
for (let i = 0; i < cases.length; i++) {
// Verify mounting 'abcde'.
act(() => root.render(<Parent>{cases[i]}</Parent>));
@@ -243,7 +243,7 @@ describe('StoreStressConcurrent', () => {
const snapshots = [];
let container = document.createElement('div');
// $FlowFixMe
let root = ReactDOM.createRoot(container);
let root = ReactDOM.unstable_createRoot(container);
for (let i = 0; i < steps.length; i++) {
act(() => root.render(<Root>{steps[i]}</Root>));
// We snapshot each step once so it doesn't regress.
@@ -316,7 +316,7 @@ describe('StoreStressConcurrent', () => {
for (let j = 0; j < steps.length; j++) {
container = document.createElement('div');
// $FlowFixMe
root = ReactDOM.createRoot(container);
root = ReactDOM.unstable_createRoot(container);
act(() => root.render(<Root>{steps[i]}</Root>));
expect(print(store)).toMatch(snapshots[i]);
act(() => root.render(<Root>{steps[j]}</Root>));
@@ -333,7 +333,7 @@ describe('StoreStressConcurrent', () => {
for (let j = 0; j < steps.length; j++) {
container = document.createElement('div');
// $FlowFixMe
root = ReactDOM.createRoot(container);
root = ReactDOM.unstable_createRoot(container);
act(() =>
root.render(
<Root>
@@ -405,7 +405,7 @@ describe('StoreStressConcurrent', () => {
const snapshots = [];
let container = document.createElement('div');
// $FlowFixMe
let root = ReactDOM.createRoot(container);
let root = ReactDOM.unstable_createRoot(container);
for (let i = 0; i < steps.length; i++) {
act(() =>
root.render(
@@ -532,7 +532,7 @@ describe('StoreStressConcurrent', () => {
// Always start with a fresh container and steps[i].
container = document.createElement('div');
// $FlowFixMe
root = ReactDOM.createRoot(container);
root = ReactDOM.unstable_createRoot(container);
act(() =>
root.render(
<Root>
@@ -578,7 +578,7 @@ describe('StoreStressConcurrent', () => {
// Always start with a fresh container and steps[i].
container = document.createElement('div');
// $FlowFixMe
root = ReactDOM.createRoot(container);
root = ReactDOM.unstable_createRoot(container);
act(() =>
root.render(
<Root>
@@ -636,7 +636,7 @@ describe('StoreStressConcurrent', () => {
// Always start with a fresh container and steps[i].
container = document.createElement('div');
// $FlowFixMe
root = ReactDOM.createRoot(container);
root = ReactDOM.unstable_createRoot(container);
act(() =>
root.render(
<Root>
@@ -686,7 +686,7 @@ describe('StoreStressConcurrent', () => {
// Always start with a fresh container and steps[i].
container = document.createElement('div');
// $FlowFixMe
root = ReactDOM.createRoot(container);
root = ReactDOM.unstable_createRoot(container);
act(() =>
root.render(
<Root>
@@ -740,7 +740,7 @@ describe('StoreStressConcurrent', () => {
// Always start with a fresh container and steps[i].
container = document.createElement('div');
// $FlowFixMe
root = ReactDOM.createRoot(container);
root = ReactDOM.unstable_createRoot(container);
act(() =>
root.render(
<Root>
@@ -894,7 +894,7 @@ describe('StoreStressConcurrent', () => {
const snapshots = [];
let container = document.createElement('div');
// $FlowFixMe
let root = ReactDOM.createRoot(container);
let root = ReactDOM.unstable_createRoot(container);
for (let i = 0; i < steps.length; i++) {
act(() =>
root.render(
@@ -1051,7 +1051,7 @@ describe('StoreStressConcurrent', () => {
// Always start with a fresh container and steps[i].
container = document.createElement('div');
// $FlowFixMe
root = ReactDOM.createRoot(container);
root = ReactDOM.unstable_createRoot(container);
act(() =>
root.render(
<Root>
@@ -1103,7 +1103,7 @@ describe('StoreStressConcurrent', () => {
// Always start with a fresh container and steps[i].
container = document.createElement('div');
// $FlowFixMe
root = ReactDOM.createRoot(container);
root = ReactDOM.unstable_createRoot(container);
act(() =>
root.render(
<Root>
@@ -1170,7 +1170,7 @@ describe('StoreStressConcurrent', () => {
// Always start with a fresh container and steps[i].
container = document.createElement('div');
// $FlowFixMe
root = ReactDOM.createRoot(container);
root = ReactDOM.unstable_createRoot(container);
act(() =>
root.render(
<Root>
@@ -1222,7 +1222,7 @@ describe('StoreStressConcurrent', () => {
// Always start with a fresh container and steps[i].
container = document.createElement('div');
// $FlowFixMe
root = ReactDOM.createRoot(container);
root = ReactDOM.unstable_createRoot(container);
act(() =>
root.render(
<Root>
@@ -1274,7 +1274,7 @@ describe('StoreStressConcurrent', () => {
// Always start with a fresh container and steps[i].
container = document.createElement('div');
// $FlowFixMe
root = ReactDOM.createRoot(container);
root = ReactDOM.unstable_createRoot(container);
act(() =>
root.render(
<Root>
+2
View File
@@ -29,7 +29,9 @@ export {
render,
unmountComponentAtNode,
createRoot,
createRoot as unstable_createRoot,
createBlockingRoot,
createBlockingRoot as unstable_createBlockingRoot,
unstable_flushControlled,
unstable_scheduleHydration,
unstable_renderSubtreeIntoContainer,
+2 -2
View File
@@ -19,8 +19,8 @@ export {
render,
unmountComponentAtNode,
// exposeConcurrentModeAPIs
createRoot,
createBlockingRoot,
createRoot as unstable_createRoot,
createBlockingRoot as unstable_createBlockingRoot,
unstable_flushControlled,
unstable_scheduleHydration,
// Disabled behind disableUnstableRenderSubtreeIntoContainer
+21 -1
View File
@@ -7,4 +7,24 @@
* @flow
*/
export * from './src/client/ReactDOM';
// Export all exports so that they're available in tests.
// We can't use export * from in Flow for some reason.
export {
createPortal,
unstable_batchedUpdates,
flushSync,
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,
version,
findDOMNode,
hydrate,
render,
unmountComponentAtNode,
createRoot,
createRoot as unstable_createRoot,
createBlockingRoot,
createBlockingRoot as unstable_createBlockingRoot,
unstable_flushControlled,
unstable_scheduleHydration,
unstable_renderSubtreeIntoContainer,
unstable_createPortal,
} from './src/client/ReactDOM';
+2
View File
@@ -14,7 +14,9 @@ export {
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,
version,
createRoot,
createRoot as unstable_createRoot,
createBlockingRoot,
createBlockingRoot as unstable_createBlockingRoot,
unstable_flushControlled,
unstable_scheduleHydration,
} from './src/client/ReactDOM';
+11 -11
View File
@@ -192,7 +192,7 @@ describe('ReactDOMFiberAsync', () => {
);
}
}
const root = ReactDOM.createRoot(container);
const root = ReactDOM.unstable_createRoot(container);
root.render(<Counter />);
Scheduler.unstable_flushAll();
expect(asyncValueRef.current.textContent).toBe('');
@@ -213,7 +213,7 @@ describe('ReactDOMFiberAsync', () => {
// @gate experimental
it('top-level updates are concurrent', () => {
const root = ReactDOM.createRoot(container);
const root = ReactDOM.unstable_createRoot(container);
root.render(<div>Hi</div>);
expect(container.textContent).toEqual('');
Scheduler.unstable_flushAll();
@@ -236,7 +236,7 @@ describe('ReactDOMFiberAsync', () => {
}
}
const root = ReactDOM.createRoot(container);
const root = ReactDOM.unstable_createRoot(container);
root.render(<Component />);
expect(container.textContent).toEqual('');
Scheduler.unstable_flushAll();
@@ -267,7 +267,7 @@ describe('ReactDOMFiberAsync', () => {
}
}
const root = ReactDOM.createRoot(container);
const root = ReactDOM.unstable_createRoot(container);
root.render(<Component />);
Scheduler.unstable_flushAll();
@@ -309,7 +309,7 @@ describe('ReactDOMFiberAsync', () => {
return this.state.counter;
}
}
const root = ReactDOM.createRoot(container);
const root = ReactDOM.unstable_createRoot(container);
root.render(<Counter />);
Scheduler.unstable_flushAll();
expect(container.textContent).toEqual('0');
@@ -423,7 +423,7 @@ describe('ReactDOMFiberAsync', () => {
}
}
const root = ReactDOM.createRoot(container);
const root = ReactDOM.unstable_createRoot(container);
root.render(<Form />);
// Flush
Scheduler.unstable_flushAll();
@@ -490,7 +490,7 @@ describe('ReactDOMFiberAsync', () => {
}
}
const root = ReactDOM.createRoot(container);
const root = ReactDOM.unstable_createRoot(container);
root.render(<Form />);
// Flush
Scheduler.unstable_flushAll();
@@ -551,7 +551,7 @@ describe('ReactDOMFiberAsync', () => {
}
}
const root = ReactDOM.createRoot(container);
const root = ReactDOM.unstable_createRoot(container);
root.render(<Form />);
// Flush
Scheduler.unstable_flushAll();
@@ -614,7 +614,7 @@ describe('ReactDOMFiberAsync', () => {
describe('createBlockingRoot', () => {
// @gate experimental
it('updates flush without yielding in the next event', () => {
const root = ReactDOM.createBlockingRoot(container);
const root = ReactDOM.unstable_createBlockingRoot(container);
function Text(props) {
Scheduler.unstable_yieldValue(props.text);
@@ -661,7 +661,7 @@ describe('ReactDOMFiberAsync', () => {
return <button ref={ref}>new</button>;
}
const oldRoot = ReactDOM.createRoot(container);
const oldRoot = ReactDOM.unstable_createRoot(container);
act(() => {
oldRoot.render(<OldApp />);
});
@@ -673,7 +673,7 @@ describe('ReactDOMFiberAsync', () => {
expect(container.textContent).toBe('');
// We can now render a new one.
const newRoot = ReactDOM.createRoot(container);
const newRoot = ReactDOM.unstable_createRoot(container);
ReactDOM.flushSync(() => {
newRoot.render(<NewApp />);
});
+1 -1
View File
@@ -126,7 +126,7 @@ describe('ReactDOMHooks', () => {
const inputRef = createRef();
const labelRef = createRef();
const root = ReactDOM.createRoot(container);
const root = ReactDOM.unstable_createRoot(container);
root.render(<Example inputRef={inputRef} labelRef={labelRef} />);
Scheduler.unstable_flushAll();
+20 -20
View File
@@ -28,13 +28,13 @@ describe('ReactDOMRoot', () => {
if (!__EXPERIMENTAL__) {
it('createRoot is not exposed in stable build', () => {
expect(ReactDOM.createRoot).toBe(undefined);
expect(ReactDOM.unstable_createRoot).toBe(undefined);
});
return;
}
it('renders children', () => {
const root = ReactDOM.createRoot(container);
const root = ReactDOM.unstable_createRoot(container);
root.render(<div>Hi</div>);
Scheduler.unstable_flushAll();
expect(container.textContent).toEqual('Hi');
@@ -42,7 +42,7 @@ describe('ReactDOMRoot', () => {
it('warns if a callback parameter is provided to render', () => {
const callback = jest.fn();
const root = ReactDOM.createRoot(container);
const root = ReactDOM.unstable_createRoot(container);
expect(() =>
root.render(<div>Hi</div>, callback),
).toErrorDev(
@@ -56,7 +56,7 @@ describe('ReactDOMRoot', () => {
it('warns if a callback parameter is provided to unmount', () => {
const callback = jest.fn();
const root = ReactDOM.createRoot(container);
const root = ReactDOM.unstable_createRoot(container);
root.render(<div>Hi</div>);
expect(() =>
root.unmount(callback),
@@ -70,7 +70,7 @@ describe('ReactDOMRoot', () => {
});
it('unmounts children', () => {
const root = ReactDOM.createRoot(container);
const root = ReactDOM.unstable_createRoot(container);
root.render(<div>Hi</div>);
Scheduler.unstable_flushAll();
expect(container.textContent).toEqual('Hi');
@@ -93,7 +93,7 @@ describe('ReactDOMRoot', () => {
// Does not hydrate by default
const container1 = document.createElement('div');
container1.innerHTML = markup;
const root1 = ReactDOM.createRoot(container1);
const root1 = ReactDOM.unstable_createRoot(container1);
root1.render(
<div>
<span />
@@ -104,7 +104,7 @@ describe('ReactDOMRoot', () => {
// Accepts `hydrate` option
const container2 = document.createElement('div');
container2.innerHTML = markup;
const root2 = ReactDOM.createRoot(container2, {hydrate: true});
const root2 = ReactDOM.unstable_createRoot(container2, {hydrate: true});
root2.render(
<div>
<span />
@@ -136,7 +136,7 @@ describe('ReactDOMRoot', () => {
it('clears existing children', async () => {
container.innerHTML = '<div>a</div><div>b</div>';
const root = ReactDOM.createRoot(container);
const root = ReactDOM.unstable_createRoot(container);
root.render(
<div>
<span>c</span>
@@ -157,12 +157,12 @@ describe('ReactDOMRoot', () => {
it('throws a good message on invalid containers', () => {
expect(() => {
ReactDOM.createRoot(<div>Hi</div>);
ReactDOM.unstable_createRoot(<div>Hi</div>);
}).toThrow('createRoot(...): Target container is not a DOM element.');
});
it('warns when rendering with legacy API into createRoot() container', () => {
const root = ReactDOM.createRoot(container);
const root = ReactDOM.unstable_createRoot(container);
root.render(<div>Hi</div>);
Scheduler.unstable_flushAll();
expect(container.textContent).toEqual('Hi');
@@ -185,7 +185,7 @@ describe('ReactDOMRoot', () => {
});
it('warns when hydrating with legacy API into createRoot() container', () => {
const root = ReactDOM.createRoot(container);
const root = ReactDOM.unstable_createRoot(container);
root.render(<div>Hi</div>);
Scheduler.unstable_flushAll();
expect(container.textContent).toEqual('Hi');
@@ -205,7 +205,7 @@ describe('ReactDOMRoot', () => {
});
it('warns when unmounting with legacy API (no previous content)', () => {
const root = ReactDOM.createRoot(container);
const root = ReactDOM.unstable_createRoot(container);
root.render(<div>Hi</div>);
Scheduler.unstable_flushAll();
expect(container.textContent).toEqual('Hi');
@@ -234,7 +234,7 @@ describe('ReactDOMRoot', () => {
// Currently createRoot().render() doesn't clear this.
container.appendChild(document.createElement('div'));
// The rest is the same as test above.
const root = ReactDOM.createRoot(container);
const root = ReactDOM.unstable_createRoot(container);
root.render(<div>Hi</div>);
Scheduler.unstable_flushAll();
expect(container.textContent).toEqual('Hi');
@@ -260,7 +260,7 @@ describe('ReactDOMRoot', () => {
it('warns when passing legacy container to createRoot()', () => {
ReactDOM.render(<div>Hi</div>, container);
expect(() => {
ReactDOM.createRoot(container);
ReactDOM.unstable_createRoot(container);
}).toErrorDev(
'You are calling ReactDOM.createRoot() on a container that was previously ' +
'passed to ReactDOM.render(). This is not supported.',
@@ -269,9 +269,9 @@ describe('ReactDOMRoot', () => {
});
it('warns when creating two roots managing the same container', () => {
ReactDOM.createRoot(container);
ReactDOM.unstable_createRoot(container);
expect(() => {
ReactDOM.createRoot(container);
ReactDOM.unstable_createRoot(container);
}).toErrorDev(
'You are calling ReactDOM.createRoot() on a container that ' +
'has already been passed to createRoot() before. Instead, call ' +
@@ -281,15 +281,15 @@ describe('ReactDOMRoot', () => {
});
it('does not warn when creating second root after first one is unmounted', () => {
const root = ReactDOM.createRoot(container);
const root = ReactDOM.unstable_createRoot(container);
root.unmount();
Scheduler.unstable_flushAll();
ReactDOM.createRoot(container); // No warning
ReactDOM.unstable_createRoot(container); // No warning
});
it('warns if creating a root on the document.body', async () => {
expect(() => {
ReactDOM.createRoot(document.body);
ReactDOM.unstable_createRoot(document.body);
}).toErrorDev(
'createRoot(): Creating roots directly with document.body is ' +
'discouraged, since its children are often manipulated by third-party ' +
@@ -301,7 +301,7 @@ describe('ReactDOMRoot', () => {
});
it('warns if updating a root that has had its contents removed', async () => {
const root = ReactDOM.createRoot(container);
const root = ReactDOM.unstable_createRoot(container);
root.render(<div>Hi</div>);
Scheduler.unstable_flushAll();
container.innerHTML = '';
@@ -1051,7 +1051,7 @@ describe('ReactDOMServerHooks', () => {
document.body.append(container);
container.innerHTML = ReactDOMServer.renderToString(<App />);
const root = ReactDOM.createRoot(container, {hydrate: true});
const root = ReactDOM.unstable_createRoot(container, {hydrate: true});
root.render(<App />);
Scheduler.unstable_flushAll();
jest.runAllTimers();
@@ -1136,7 +1136,7 @@ describe('ReactDOMServerHooks', () => {
const container = document.createElement('div');
document.body.append(container);
container.innerHTML = ReactDOMServer.renderToString(<App />);
const root = ReactDOM.createRoot(container, {hydrate: true});
const root = ReactDOM.unstable_createRoot(container, {hydrate: true});
ReactTestUtils.act(() => {
root.render(<App />);
});
@@ -1196,7 +1196,7 @@ describe('ReactDOMServerHooks', () => {
const container = document.createElement('div');
document.body.append(container);
container.innerHTML = ReactDOMServer.renderToString(<App />);
const root = ReactDOM.createRoot(container, {hydrate: true});
const root = ReactDOM.unstable_createRoot(container, {hydrate: true});
ReactTestUtils.act(() => {
root.render(<App />);
});
@@ -1291,7 +1291,7 @@ describe('ReactDOMServerHooks', () => {
const childOneSpan = container.getElementsByTagName('span')[0];
const root = ReactDOM.createRoot(container, {hydrate: true});
const root = ReactDOM.unstable_createRoot(container, {hydrate: true});
root.render(<App show={false} />);
expect(Scheduler).toHaveYielded([]);
@@ -1397,7 +1397,7 @@ describe('ReactDOMServerHooks', () => {
container.innerHTML = ReactDOMServer.renderToString(<App />);
suspend = true;
const root = ReactDOM.createRoot(container, {hydrate: true});
const root = ReactDOM.unstable_createRoot(container, {hydrate: true});
await ReactTestUtils.act(async () => {
root.render(<App />);
});
@@ -1447,7 +1447,9 @@ describe('ReactDOMServerHooks', () => {
// This is the wrong HTML string
container.innerHTML = '<span></span>';
ReactDOM.createRoot(container, {hydrate: true}).render(<App />);
ReactDOM.unstable_createRoot(container, {hydrate: true}).render(
<App />,
);
expect(() =>
expect(() => Scheduler.unstable_flushAll()).toThrow(),
).toErrorDev([
@@ -1497,7 +1499,7 @@ describe('ReactDOMServerHooks', () => {
container.innerHTML = ReactDOMServer.renderToString(<App />);
suspend = false;
const root = ReactDOM.createRoot(container, {hydrate: true});
const root = ReactDOM.unstable_createRoot(container, {hydrate: true});
await ReactTestUtils.act(async () => {
root.render(<App />);
});
@@ -1533,7 +1535,9 @@ describe('ReactDOMServerHooks', () => {
// This is the wrong HTML string
container.innerHTML = '<span></span>';
ReactDOM.createRoot(container, {hydrate: true}).render(<App />);
ReactDOM.unstable_createRoot(container, {hydrate: true}).render(
<App />,
);
expect(() =>
expect(() => Scheduler.unstable_flushAll()).toThrow(),
).toErrorDev([
@@ -1555,7 +1559,9 @@ describe('ReactDOMServerHooks', () => {
// This is the wrong HTML string
container.innerHTML = '<span></span>';
ReactDOM.createRoot(container, {hydrate: true}).render(<App />);
ReactDOM.unstable_createRoot(container, {hydrate: true}).render(
<App />,
);
expect(() =>
expect(() => Scheduler.unstable_flushAll()).toThrow(
'The object passed back from useOpaqueIdentifier is meant to be passed through to attributes only. ' +
@@ -1584,7 +1590,9 @@ describe('ReactDOMServerHooks', () => {
// This is the wrong HTML string
container.innerHTML = '<span></span>';
ReactDOM.createRoot(container, {hydrate: true}).render(<App />);
ReactDOM.unstable_createRoot(container, {hydrate: true}).render(
<App />,
);
expect(() =>
expect(() => Scheduler.unstable_flushAll()).toThrow(
'The object passed back from useOpaqueIdentifier is meant to be passed through to attributes only. ' +
@@ -1612,7 +1620,9 @@ describe('ReactDOMServerHooks', () => {
document.body.appendChild(container);
container.innerHTML = ReactDOMServer.renderToString(<App />);
ReactDOM.createRoot(container, {hydrate: true}).render(<App />);
ReactDOM.unstable_createRoot(container, {hydrate: true}).render(
<App />,
);
expect(() =>
expect(() => Scheduler.unstable_flushAll()).toThrow(
'The object passed back from useOpaqueIdentifier is meant to be passed through to attributes only. ' +
@@ -1637,7 +1647,9 @@ describe('ReactDOMServerHooks', () => {
document.body.appendChild(container);
container.innerHTML = ReactDOMServer.renderToString(<App />);
ReactDOM.createRoot(container, {hydrate: true}).render(<App />);
ReactDOM.unstable_createRoot(container, {hydrate: true}).render(
<App />,
);
expect(() =>
expect(() => Scheduler.unstable_flushAll()).toThrow(
'The object passed back from useOpaqueIdentifier is meant to be passed through to attributes only. ' +
@@ -1670,7 +1682,9 @@ describe('ReactDOMServerHooks', () => {
container.innerHTML = ReactDOMServer.renderToString(<App />);
ReactDOM.createRoot(container, {hydrate: true}).render(<App />);
ReactDOM.unstable_createRoot(container, {hydrate: true}).render(
<App />,
);
if (gate(flags => flags.new)) {
expect(() => Scheduler.unstable_flushAll()).toErrorDev([
@@ -1712,7 +1726,9 @@ describe('ReactDOMServerHooks', () => {
container.innerHTML = ReactDOMServer.renderToString(<App />);
ReactDOM.createRoot(container, {hydrate: true}).render(<App />);
ReactDOM.unstable_createRoot(container, {hydrate: true}).render(
<App />,
);
if (gate(flags => flags.new)) {
expect(() => Scheduler.unstable_flushAll()).toErrorDev([
@@ -1772,7 +1788,9 @@ describe('ReactDOMServerHooks', () => {
.getAttribute('aria-labelledby'),
).toEqual(serverID);
ReactDOM.createRoot(container, {hydrate: true}).render(<App />);
ReactDOM.unstable_createRoot(container, {hydrate: true}).render(
<App />,
);
jest.runAllTimers();
expect(Scheduler).toHaveYielded([]);
expect(Scheduler).toFlushAndYield([]);
@@ -496,7 +496,7 @@ describe('ReactDOMServerHydration', () => {
const finalHTML = ReactDOMServer.renderToString(<div />);
const container = document.createElement('div');
container.innerHTML = finalHTML;
const root = ReactDOM.createRoot(container, {hydrate: true});
const root = ReactDOM.unstable_createRoot(container, {hydrate: true});
root.render(<div />);
Scheduler.unstable_flushAll();
root.render(null);
+8 -4
View File
@@ -30,7 +30,7 @@ describe('ReactTestUtils.act()', () => {
if (__EXPERIMENTAL__) {
let concurrentRoot = null;
const renderConcurrent = (el, dom) => {
concurrentRoot = ReactDOM.createRoot(dom);
concurrentRoot = ReactDOM.unstable_createRoot(dom);
concurrentRoot.render(el);
};
@@ -76,7 +76,7 @@ describe('ReactTestUtils.act()', () => {
if (__EXPERIMENTAL__) {
let blockingRoot = null;
const renderBatched = (el, dom) => {
blockingRoot = ReactDOM.createBlockingRoot(dom);
blockingRoot = ReactDOM.unstable_createBlockingRoot(dom);
blockingRoot.render(el);
};
@@ -127,7 +127,9 @@ describe('ReactTestUtils.act()', () => {
// @gate experimental
it('warns in blocking mode', () => {
expect(() => {
const root = ReactDOM.createBlockingRoot(document.createElement('div'));
const root = ReactDOM.unstable_createBlockingRoot(
document.createElement('div'),
);
root.render(<App />);
Scheduler.unstable_flushAll();
}).toErrorDev([
@@ -138,7 +140,9 @@ describe('ReactTestUtils.act()', () => {
// @gate experimental
it('warns in concurrent mode', () => {
expect(() => {
const root = ReactDOM.createRoot(document.createElement('div'));
const root = ReactDOM.unstable_createRoot(
document.createElement('div'),
);
root.render(<App />);
Scheduler.unstable_flushAll();
}).toErrorDev([
@@ -30,7 +30,7 @@ it('does not warn when rendering in legacy mode', () => {
// @gate experimental
it('should warn when rendering in concurrent mode', () => {
expect(() => {
ReactDOM.createRoot(document.createElement('div')).render(<App />);
ReactDOM.unstable_createRoot(document.createElement('div')).render(<App />);
}).toErrorDev(
'In Concurrent or Sync modes, the "scheduler" module needs to be mocked ' +
'to guarantee consistent behaviour across tests and browsers.',
@@ -38,14 +38,16 @@ it('should warn when rendering in concurrent mode', () => {
);
// does not warn twice
expect(() => {
ReactDOM.createRoot(document.createElement('div')).render(<App />);
ReactDOM.unstable_createRoot(document.createElement('div')).render(<App />);
}).toErrorDev([]);
});
// @gate experimental
it('should warn when rendering in blocking mode', () => {
expect(() => {
ReactDOM.createBlockingRoot(document.createElement('div')).render(<App />);
ReactDOM.unstable_createBlockingRoot(document.createElement('div')).render(
<App />,
);
}).toErrorDev(
'In Concurrent or Sync modes, the "scheduler" module needs to be mocked ' +
'to guarantee consistent behaviour across tests and browsers.',
@@ -53,6 +55,8 @@ it('should warn when rendering in blocking mode', () => {
);
// does not warn twice
expect(() => {
ReactDOM.createBlockingRoot(document.createElement('div')).render(<App />);
ReactDOM.unstable_createBlockingRoot(document.createElement('div')).render(
<App />,
);
}).toErrorDev([]);
});
+1 -1
View File
@@ -1319,7 +1319,7 @@ describe('ReactUpdates', () => {
);
}
const root = ReactDOM.createRoot(container);
const root = ReactDOM.unstable_createRoot(container);
let hiddenDiv;
act(() => {
root.render(<Foo />);
@@ -479,7 +479,7 @@ describe('ChangeEventPlugin', () => {
// @gate experimental
it('text input', () => {
const root = ReactDOM.createRoot(container);
const root = ReactDOM.unstable_createRoot(container);
let input;
class ControlledInput extends React.Component {
@@ -522,7 +522,7 @@ describe('ChangeEventPlugin', () => {
// @gate experimental
it('checkbox input', () => {
const root = ReactDOM.createRoot(container);
const root = ReactDOM.unstable_createRoot(container);
let input;
class ControlledInput extends React.Component {
@@ -578,7 +578,7 @@ describe('ChangeEventPlugin', () => {
// @gate experimental
it('textarea', () => {
const root = ReactDOM.createRoot(container);
const root = ReactDOM.unstable_createRoot(container);
let textarea;
class ControlledTextarea extends React.Component {
@@ -621,7 +621,7 @@ describe('ChangeEventPlugin', () => {
// @gate experimental
it('parent of input', () => {
const root = ReactDOM.createRoot(container);
const root = ReactDOM.unstable_createRoot(container);
let input;
class ControlledInput extends React.Component {
@@ -668,7 +668,7 @@ describe('ChangeEventPlugin', () => {
// @gate experimental
it('is async for non-input events', () => {
const root = ReactDOM.createRoot(container);
const root = ReactDOM.unstable_createRoot(container);
let input;
class ControlledInput extends React.Component {
@@ -728,7 +728,7 @@ describe('ChangeEventPlugin', () => {
const {act} = TestUtils;
const {useState} = React;
const root = ReactDOM.createRoot(container);
const root = ReactDOM.unstable_createRoot(container);
const target = React.createRef(null);
function Foo() {
@@ -239,7 +239,7 @@ describe('SimpleEventPlugin', function() {
// @gate experimental
it('flushes pending interactive work before extracting event handler', () => {
container = document.createElement('div');
const root = ReactDOM.createRoot(container);
const root = ReactDOM.unstable_createRoot(container);
document.body.appendChild(container);
let button;
@@ -310,7 +310,7 @@ describe('SimpleEventPlugin', function() {
// @gate experimental
it('end result of many interactive updates is deterministic', () => {
container = document.createElement('div');
const root = ReactDOM.createRoot(container);
const root = ReactDOM.unstable_createRoot(container);
document.body.appendChild(container);
let button;
@@ -409,7 +409,7 @@ describe('SimpleEventPlugin', function() {
}
// Initial mount
const root = ReactDOM.createRoot(container);
const root = ReactDOM.unstable_createRoot(container);
root.render(<Wrapper />);
expect(Scheduler).toFlushAndYield([
'High-pri count: 0, Low-pri count: 0',
@@ -24,7 +24,7 @@ describe('ReactBlocks', () => {
React = require('react');
ReactNoop = require('react-noop-renderer');
block = React.block;
block = React.unstable_block;
useState = React.useState;
Suspense = React.Suspense;
const cache = new Map();
@@ -63,8 +63,8 @@ describe('ReactHooksWithNoopRenderer', () => {
useImperativeHandle = React.useImperativeHandle;
forwardRef = React.forwardRef;
memo = React.memo;
useTransition = React.useTransition;
useDeferredValue = React.useDeferredValue;
useTransition = React.unstable_useTransition;
useDeferredValue = React.unstable_useDeferredValue;
Suspense = React.Suspense;
act = ReactNoop.act;
@@ -12,7 +12,7 @@ describe('ReactSuspenseList', () => {
ReactNoop = require('react-noop-renderer');
Scheduler = require('scheduler');
Suspense = React.Suspense;
SuspenseList = React.SuspenseList;
SuspenseList = React.unstable_SuspenseList;
});
function Text(props) {
@@ -2737,7 +2737,9 @@ describe('ReactSuspenseWithNoopRenderer', () => {
function App() {
const [page, setPage] = React.useState('A');
const [startLoading, isLoading] = React.useTransition(SUSPENSE_CONFIG);
const [startLoading, isLoading] = React.unstable_useTransition(
SUSPENSE_CONFIG,
);
transitionToPage = nextPage => startLoading(() => setPage(nextPage));
return (
<Fragment>
@@ -3718,7 +3720,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
// @gate experimental
it('regression: ping at high priority causes update to be dropped', async () => {
const {useState, useTransition} = React;
const {useState, unstable_useTransition: useTransition} = React;
let setTextA;
function A() {
@@ -3836,10 +3838,10 @@ describe('ReactSuspenseWithNoopRenderer', () => {
let setTextWithLongTransition;
function App() {
const [startShortTransition, isPending1] = React.useTransition({
const [startShortTransition, isPending1] = React.unstable_useTransition({
timeoutMs: 5000,
});
const [startLongTransition, isPending2] = React.useTransition({
const [startLongTransition, isPending2] = React.unstable_useTransition({
timeoutMs: 30000,
});
const isPending = isPending1 || isPending2;
@@ -25,7 +25,7 @@ describe('ReactTransition', () => {
ReactNoop = require('react-noop-renderer');
Scheduler = require('scheduler');
useState = React.useState;
useTransition = React.useTransition;
useTransition = React.unstable_useTransition;
Suspense = React.Suspense;
act = ReactNoop.act;
});
+1 -1
View File
@@ -2424,7 +2424,7 @@ describe('ReactFresh', () => {
};
});
const root = ReactDOM.createRoot(container);
const root = ReactDOM.unstable_createRoot(container);
root.render(<AppV1 offscreen={true} />);
expect(Scheduler).toFlushAndYieldThrough(['App#layout']);
const el = container.firstChild;
@@ -183,7 +183,7 @@ describe('ReactFlightDOM', () => {
const response = ReactTransportDOMClient.createFromReadableStream(readable);
const container = document.createElement('div');
const root = ReactDOM.createRoot(container);
const root = ReactDOM.unstable_createRoot(container);
await act(async () => {
root.render(<App response={response} />);
});
@@ -222,7 +222,7 @@ describe('ReactFlightDOM', () => {
const response = ReactTransportDOMClient.createFromReadableStream(readable);
const container = document.createElement('div');
const root = ReactDOM.createRoot(container);
const root = ReactDOM.unstable_createRoot(container);
await act(async () => {
root.render(<App response={response} />);
});
@@ -259,7 +259,7 @@ describe('ReactFlightDOM', () => {
const response = ReactTransportDOMClient.createFromReadableStream(readable);
const container = document.createElement('div');
const root = ReactDOM.createRoot(container);
const root = ReactDOM.unstable_createRoot(container);
await act(async () => {
root.render(<App response={response} />);
});
@@ -418,7 +418,7 @@ describe('ReactFlightDOM', () => {
const response = ReactTransportDOMClient.createFromReadableStream(readable);
const container = document.createElement('div');
const root = ReactDOM.createRoot(container);
const root = ReactDOM.unstable_createRoot(container);
await act(async () => {
root.render(<ProfilePage response={response} />);
});
+4
View File
@@ -40,11 +40,15 @@ export {
createFactory,
// exposeConcurrentModeAPIs
useTransition,
useTransition as unstable_useTransition,
useDeferredValue,
useDeferredValue as unstable_useDeferredValue,
SuspenseList,
SuspenseList as unstable_SuspenseList,
unstable_withSuspenseConfig,
// enableBlocksAPI
block,
block as unstable_block,
// enableDeprecatedFlareAPI
DEPRECATED_useResponder,
DEPRECATED_createResponder,
+4 -4
View File
@@ -39,12 +39,12 @@ export {
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,
createFactory,
// exposeConcurrentModeAPIs
useTransition,
useDeferredValue,
SuspenseList,
useTransition as unstable_useTransition,
useDeferredValue as unstable_useDeferredValue,
SuspenseList as unstable_SuspenseList,
unstable_withSuspenseConfig,
// enableBlocksAPI
block,
block as unstable_block,
unstable_useOpaqueIdentifier,
// enableDebugTracing
unstable_DebugTracingMode,
+4
View File
@@ -69,10 +69,14 @@ export {
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,
createFactory,
useTransition,
useTransition as unstable_useTransition,
useDeferredValue,
useDeferredValue as unstable_useDeferredValue,
SuspenseList,
SuspenseList as unstable_SuspenseList,
unstable_withSuspenseConfig,
block,
block as unstable_block,
DEPRECATED_useResponder,
DEPRECATED_createResponder,
unstable_createFundamental,
+4
View File
@@ -39,11 +39,15 @@ export {
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,
// exposeConcurrentModeAPIs
useTransition,
useTransition as unstable_useTransition,
useDeferredValue,
useDeferredValue as unstable_useDeferredValue,
SuspenseList,
SuspenseList as unstable_SuspenseList,
unstable_withSuspenseConfig,
// enableBlocksAPI
block,
block as unstable_block,
// enableDeprecatedFlareAPI
DEPRECATED_useResponder,
DEPRECATED_createResponder,
@@ -398,7 +398,7 @@ describe('Concurrent Mode', () => {
}
const container = document.createElement('div');
const root = ReactDOM.createRoot(container);
const root = ReactDOM.unstable_createRoot(container);
root.render(<AsyncRoot />);
expect(() => Scheduler.unstable_flushAll()).toErrorDev(
[
@@ -454,7 +454,7 @@ Please update the following components: AsyncRoot`,
}
const container = document.createElement('div');
const root = ReactDOM.createRoot(container);
const root = ReactDOM.unstable_createRoot(container);
root.render(<AsyncRoot />);
expect(() => {
@@ -533,7 +533,7 @@ Please update the following components: Parent`,
}
const container = document.createElement('div');
const root = ReactDOM.createRoot(container);
const root = ReactDOM.unstable_createRoot(container);
root.render(<AsyncRoot foo={true} />);
expect(() =>
Scheduler.unstable_flushAll(),