Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
11565a207c |
40
CHANGELOG.md
40
CHANGELOG.md
@@ -6,46 +6,6 @@
|
||||
</summary>
|
||||
</details>
|
||||
|
||||
## 16.8.4 (March 5, 2019)
|
||||
|
||||
### React DOM and other renderers
|
||||
|
||||
- Fix a bug where DevTools caused a runtime error when inspecting a component that used a `useContext` hook. ([@bvaughn](https://github.com/bvaughn) in [#14940](https://github.com/facebook/react/pull/14940))
|
||||
|
||||
## 16.8.3 (February 21, 2019)
|
||||
|
||||
### React DOM
|
||||
|
||||
- Fix a bug that caused inputs to behave incorrectly in UMD builds. ([@gaearon](https://github.com/gaearon) in [#14914](https://github.com/facebook/react/pull/14914))
|
||||
- Fix a bug that caused render phase updates to be discarded. ([@gaearon](https://github.com/gaearon) in [#14852](https://github.com/facebook/react/pull/14852))
|
||||
|
||||
### React DOM Server
|
||||
- Unwind the context stack when a stream is destroyed without completing, to prevent incorrect values during a subsequent render. ([@overlookmotel](https://github.com/overlookmotel) in [#14706](https://github.com/facebook/react/pull/14706/))
|
||||
|
||||
## 16.8.2 (February 14, 2019)
|
||||
|
||||
### React DOM
|
||||
|
||||
* Fix `ReactDOM.render` being ignored inside `useEffect`. ([@gaearon](https://github.com/gaearon) in [#14799](https://github.com/facebook/react/pull/14799))
|
||||
* Fix a crash when unmounting empty portals. ([@gaearon](https://github.com/gaearon) in [#14820](https://github.com/facebook/react/pull/14820))
|
||||
* Fix `useImperativeHandle` to work correctly when no deps are specified. ([@gaearon](https://github.com/gaearon) in [#14801](https://github.com/facebook/react/pull/14801))
|
||||
* Fix `crossOrigin` attribute to work in SVG `image` elements. ([@aweary](https://github.com/aweary) in [#14832](https://github.com/facebook/react/pull/14832))
|
||||
* Fix a false positive warning when using Suspense with Hooks. ([@gaearon](https://github.com/gaearon) in [#14821](https://github.com/facebook/react/pull/14821))
|
||||
|
||||
### React Test Utils and React Test Renderer
|
||||
|
||||
* Include component stack into the `act()` warning. ([@threepointone](https://github.com/threepointone) in [#14855](https://github.com/facebook/react/pull/14855))
|
||||
|
||||
## 16.8.1 (February 6, 2019)
|
||||
|
||||
### React DOM and React Test Renderer
|
||||
|
||||
* Fix a crash when used together with an older version of React. ([@bvaughn](https://github.com/bvaughn) in [#14770](https://github.com/facebook/react/pull/14770))
|
||||
|
||||
### React Test Utils
|
||||
|
||||
* Fix a crash in Node environment. ([@threepointone](https://github.com/threepointone) in [#14768](https://github.com/facebook/react/pull/14768))
|
||||
|
||||
## 16.8.0 (February 6, 2019)
|
||||
|
||||
### React
|
||||
|
||||
@@ -40,8 +40,10 @@ You can improve it by sending pull requests to [this repository](https://github.
|
||||
We have several examples [on the website](https://reactjs.org/). Here is the first one to get you started:
|
||||
|
||||
```jsx
|
||||
function HelloMessage({ name }) {
|
||||
return <div>Hello {name}</div>;
|
||||
class HelloMessage extends React.Component {
|
||||
render() {
|
||||
return <div>Hello {this.props.name}</div>;
|
||||
}
|
||||
}
|
||||
|
||||
ReactDOM.render(
|
||||
|
||||
@@ -1,32 +1,17 @@
|
||||
import React, {useContext, useState, Suspense} from 'react';
|
||||
import React, {Component} from 'react';
|
||||
|
||||
import Chrome from './Chrome';
|
||||
import Page from './Page';
|
||||
import Page2 from './Page2';
|
||||
import Theme from './Theme';
|
||||
|
||||
function LoadingIndicator() {
|
||||
let theme = useContext(Theme);
|
||||
return <div className={theme + '-loading'}>Loading...</div>;
|
||||
}
|
||||
|
||||
export default function App({assets}) {
|
||||
let [CurrentPage, switchPage] = useState(() => Page);
|
||||
return (
|
||||
<Chrome title="Hello World" assets={assets}>
|
||||
<div>
|
||||
<h1>Hello World</h1>
|
||||
<a className="link" onClick={() => switchPage(() => Page)}>
|
||||
Page 1
|
||||
</a>
|
||||
{' | '}
|
||||
<a className="link" onClick={() => switchPage(() => Page2)}>
|
||||
Page 2
|
||||
</a>
|
||||
<Suspense fallback={<LoadingIndicator />}>
|
||||
<CurrentPage />
|
||||
</Suspense>
|
||||
</div>
|
||||
</Chrome>
|
||||
);
|
||||
export default class App extends Component {
|
||||
render() {
|
||||
return (
|
||||
<Chrome title="Hello World" assets={this.props.assets}>
|
||||
<div>
|
||||
<h1>Hello World</h1>
|
||||
<Page />
|
||||
</div>
|
||||
</Chrome>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,27 +3,3 @@ body {
|
||||
padding: 0;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
body.light {
|
||||
background-color: #FFFFFF;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
body.dark {
|
||||
background-color: #000000;
|
||||
color: #CCCCCC;
|
||||
}
|
||||
|
||||
.light-loading {
|
||||
margin: 10px 0;
|
||||
padding: 10px;
|
||||
background-color: #CCCCCC;
|
||||
color: #666666;
|
||||
}
|
||||
|
||||
.dark-loading {
|
||||
margin: 10px 0;
|
||||
padding: 10px;
|
||||
background-color: #333333;
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
import React, {Component} from 'react';
|
||||
|
||||
import Theme, {ThemeToggleButton} from './Theme';
|
||||
|
||||
import './Chrome.css';
|
||||
|
||||
export default class Chrome extends Component {
|
||||
state = {theme: 'light'};
|
||||
render() {
|
||||
const assets = this.props.assets;
|
||||
return (
|
||||
@@ -17,18 +14,13 @@ export default class Chrome extends Component {
|
||||
<link rel="stylesheet" href={assets['main.css']} />
|
||||
<title>{this.props.title}</title>
|
||||
</head>
|
||||
<body className={this.state.theme}>
|
||||
<body>
|
||||
<noscript
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `<b>Enable JavaScript to run this app.</b>`,
|
||||
}}
|
||||
/>
|
||||
<Theme.Provider value={this.state.theme}>
|
||||
{this.props.children}
|
||||
<div>
|
||||
<ThemeToggleButton onChange={theme => this.setState({theme})} />
|
||||
</div>
|
||||
</Theme.Provider>
|
||||
{this.props.children}
|
||||
<script
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `assetManifest = ${JSON.stringify(assets)};`,
|
||||
|
||||
@@ -1,16 +1,3 @@
|
||||
.link {
|
||||
.bold {
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
}
|
||||
.light-box {
|
||||
margin: 10px 0;
|
||||
padding: 10px;
|
||||
background-color: #CCCCCC;
|
||||
color: #333333;
|
||||
}
|
||||
.dark-box {
|
||||
margin: 10px 0;
|
||||
padding: 10px;
|
||||
background-color: #333333;
|
||||
color: #CCCCCC;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
import React, {Component} from 'react';
|
||||
|
||||
import Theme from './Theme';
|
||||
import Suspend from './Suspend';
|
||||
|
||||
import './Page.css';
|
||||
|
||||
const autofocusedInputs = [
|
||||
@@ -17,22 +14,17 @@ export default class Page extends Component {
|
||||
};
|
||||
render() {
|
||||
const link = (
|
||||
<a className="link" onClick={this.handleClick}>
|
||||
<a className="bold" onClick={this.handleClick}>
|
||||
Click Here
|
||||
</a>
|
||||
);
|
||||
return (
|
||||
<div className={this.context + '-box'}>
|
||||
<Suspend>
|
||||
<p suppressHydrationWarning={true}>
|
||||
A random number: {Math.random()}
|
||||
</p>
|
||||
<p>Autofocus on page load: {autofocusedInputs}</p>
|
||||
<p>{!this.state.active ? link : 'Thanks!'}</p>
|
||||
{this.state.active && <p>Autofocus on update: {autofocusedInputs}</p>}
|
||||
</Suspend>
|
||||
<div>
|
||||
<p suppressHydrationWarning={true}>A random number: {Math.random()}</p>
|
||||
<p>Autofocus on page load: {autofocusedInputs}</p>
|
||||
<p>{!this.state.active ? link : 'Thanks!'}</p>
|
||||
{this.state.active && <p>Autofocus on update: {autofocusedInputs}</p>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
Page.contextType = Theme;
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
import React, {useContext} from 'react';
|
||||
|
||||
import Theme from './Theme';
|
||||
import Suspend from './Suspend';
|
||||
|
||||
import './Page.css';
|
||||
|
||||
export default function Page2() {
|
||||
let theme = useContext(Theme);
|
||||
return (
|
||||
<div className={theme + '-box'}>
|
||||
<Suspend>Content of a different page</Suspend>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
let promise = null;
|
||||
let isResolved = false;
|
||||
|
||||
export default function Suspend({children}) {
|
||||
// This will suspend the content from rendering but only on the client.
|
||||
// This is used to demo a slow loading app.
|
||||
if (typeof window === 'object') {
|
||||
if (!isResolved) {
|
||||
if (promise === null) {
|
||||
promise = new Promise(resolve => {
|
||||
setTimeout(() => {
|
||||
isResolved = true;
|
||||
resolve();
|
||||
}, 6000);
|
||||
});
|
||||
}
|
||||
throw promise;
|
||||
}
|
||||
}
|
||||
return children;
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
import React, {createContext, useContext, useState} from 'react';
|
||||
|
||||
const Theme = createContext('light');
|
||||
|
||||
export default Theme;
|
||||
|
||||
export function ThemeToggleButton({onChange}) {
|
||||
let theme = useContext(Theme);
|
||||
let [targetTheme, setTargetTheme] = useState(theme);
|
||||
function toggleTheme() {
|
||||
let newTheme = theme === 'light' ? 'dark' : 'light';
|
||||
// High pri, responsive update.
|
||||
setTargetTheme(newTheme);
|
||||
// Perform the actual theme change in a separate update.
|
||||
setTimeout(() => onChange(newTheme), 0);
|
||||
}
|
||||
if (targetTheme !== theme) {
|
||||
return 'Switching to ' + targetTheme + '...';
|
||||
}
|
||||
return (
|
||||
<a className="link" onClick={toggleTheme}>
|
||||
Switch to {theme === 'light' ? 'Dark' : 'Light'} theme
|
||||
</a>
|
||||
);
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
import React from 'react';
|
||||
import {unstable_createRoot} from 'react-dom';
|
||||
import {hydrate} from 'react-dom';
|
||||
|
||||
import App from './components/App';
|
||||
|
||||
let root = unstable_createRoot(document, {hydrate: true});
|
||||
root.render(<App assets={window.assetManifest} />);
|
||||
hydrate(<App assets={window.assetManifest} />, document);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "create-subscription",
|
||||
"description": "utility for subscribing to external data sources inside React components",
|
||||
"version": "16.8.4",
|
||||
"version": "16.8.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/facebook/react.git",
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
{
|
||||
"private": true,
|
||||
"name": "eslint-plugin-react-hooks",
|
||||
"description": "ESLint rules for React Hooks",
|
||||
"version": "1.1.0",
|
||||
"version": "1.0.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/facebook/react.git",
|
||||
"type" : "git",
|
||||
"url" : "https://github.com/facebook/react.git",
|
||||
"directory": "packages/eslint-plugin-react-hooks"
|
||||
},
|
||||
"files": [
|
||||
|
||||
@@ -293,7 +293,7 @@ export default {
|
||||
// hook functions.
|
||||
const codePathFunctionName = getFunctionName(codePathNode);
|
||||
|
||||
// This is a valid code path for React hooks if we are directly in a React
|
||||
// This is a valid code path for React hooks if we are direcly in a React
|
||||
// function component or we are in a hook function.
|
||||
const isSomewhereInsideComponentOrHook = isInsideComponentOrHook(
|
||||
codePathNode,
|
||||
@@ -422,7 +422,7 @@ export default {
|
||||
// false positives due to feature flag checks. We're less
|
||||
// sensitive to them in classes because hooks would produce
|
||||
// runtime errors in classes anyway, and because a use*()
|
||||
// call in a class, if it works, is unambiguously *not* a hook.
|
||||
// call in a class, if it works, is unambigously *not* a hook.
|
||||
} else if (codePathFunctionName) {
|
||||
// Custom message if we found an invalid function name.
|
||||
const message =
|
||||
@@ -476,7 +476,7 @@ export default {
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the static name of a function AST node. For function declarations it is
|
||||
* Gets tbe static name of a function AST node. For function declarations it is
|
||||
* easy. For anonymous function expressions it is much harder. If you search for
|
||||
* `IsAnonymousFunctionDefinition()` in the ECMAScript spec you'll find places
|
||||
* where JS gives anonymous function expressions names. We roughly detect the
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "jest-react",
|
||||
"version": "0.6.4",
|
||||
"version": "0.6.0",
|
||||
"description": "Jest matchers and utilities for testing React components.",
|
||||
"main": "index.js",
|
||||
"repository": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "react-art",
|
||||
"description": "React ART is a JavaScript library for drawing vector graphics using React. It provides declarative and reactive bindings to the ART library. Using the same declarative API you can render the output to either Canvas, SVG or VML (IE8).",
|
||||
"version": "16.8.4",
|
||||
"version": "16.8.0",
|
||||
"main": "index.js",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -27,7 +27,7 @@
|
||||
"loose-envify": "^1.1.0",
|
||||
"object-assign": "^4.1.1",
|
||||
"prop-types": "^15.6.2",
|
||||
"scheduler": "^0.13.4"
|
||||
"scheduler": "^0.13.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.0.0"
|
||||
|
||||
@@ -24,8 +24,6 @@ describe('ReactCache', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
|
||||
let currentPriorityLevel = 3;
|
||||
|
||||
jest.mock('scheduler', () => {
|
||||
let callbacks = [];
|
||||
return {
|
||||
@@ -40,26 +38,6 @@ describe('ReactCache', () => {
|
||||
callback();
|
||||
}
|
||||
},
|
||||
|
||||
unstable_ImmediatePriority: 1,
|
||||
unstable_UserBlockingPriority: 2,
|
||||
unstable_NormalPriority: 3,
|
||||
unstable_LowPriority: 4,
|
||||
unstable_IdlePriority: 5,
|
||||
|
||||
unstable_runWithPriority(priorityLevel, fn) {
|
||||
const prevPriorityLevel = currentPriorityLevel;
|
||||
currentPriorityLevel = priorityLevel;
|
||||
try {
|
||||
return fn();
|
||||
} finally {
|
||||
currentPriorityLevel = prevPriorityLevel;
|
||||
}
|
||||
},
|
||||
|
||||
unstable_getCurrentPriorityLevel() {
|
||||
return currentPriorityLevel;
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@@ -427,42 +427,4 @@ describe('ReactHooksInspectionIntegration', () => {
|
||||
expect(setterCalls[0]).not.toBe(initial);
|
||||
expect(setterCalls[1]).toBe(initial);
|
||||
});
|
||||
|
||||
// This test case is based on an open source bug report:
|
||||
// facebookincubator/redux-react-hook/issues/34#issuecomment-466693787
|
||||
it('should properly advance the current hook for useContext', () => {
|
||||
const MyContext = React.createContext(1);
|
||||
|
||||
let incrementCount;
|
||||
|
||||
function Foo(props) {
|
||||
const context = React.useContext(MyContext);
|
||||
const [data, setData] = React.useState({count: context});
|
||||
|
||||
incrementCount = () => setData(({count}) => ({count: count + 1}));
|
||||
|
||||
return <div>count: {data.count}</div>;
|
||||
}
|
||||
|
||||
const renderer = ReactTestRenderer.create(<Foo />);
|
||||
expect(renderer.toJSON()).toEqual({
|
||||
type: 'div',
|
||||
props: {},
|
||||
children: ['count: ', '1'],
|
||||
});
|
||||
|
||||
act(incrementCount);
|
||||
expect(renderer.toJSON()).toEqual({
|
||||
type: 'div',
|
||||
props: {},
|
||||
children: ['count: ', '2'],
|
||||
});
|
||||
|
||||
const childFiber = renderer.root._currentFiber();
|
||||
const tree = ReactDebugTools.inspectHooksOfFiber(childFiber);
|
||||
expect(tree).toEqual([
|
||||
{name: 'Context', value: 1, subHooks: []},
|
||||
{name: 'State', value: {count: 2}, subHooks: []},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "react-dom",
|
||||
"version": "16.8.4",
|
||||
"version": "16.8.0",
|
||||
"description": "React package for working with the DOM.",
|
||||
"main": "index.js",
|
||||
"repository": {
|
||||
@@ -20,7 +20,7 @@
|
||||
"loose-envify": "^1.1.0",
|
||||
"object-assign": "^4.1.1",
|
||||
"prop-types": "^15.6.2",
|
||||
"scheduler": "^0.13.4"
|
||||
"scheduler": "^0.13.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.0.0"
|
||||
|
||||
@@ -461,40 +461,6 @@ describe('ReactDOMFiber', () => {
|
||||
expect(container.innerHTML).toBe('<div></div>');
|
||||
});
|
||||
|
||||
it('should unmount empty portal component wherever it appears', () => {
|
||||
const portalContainer = document.createElement('div');
|
||||
|
||||
class Wrapper extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
show: true,
|
||||
};
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
{this.state.show && (
|
||||
<React.Fragment>
|
||||
{ReactDOM.createPortal(null, portalContainer)}
|
||||
<div>child</div>
|
||||
</React.Fragment>
|
||||
)}
|
||||
<div>parent</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const instance = ReactDOM.render(<Wrapper />, container);
|
||||
expect(container.innerHTML).toBe(
|
||||
'<div><div>child</div><div>parent</div></div>',
|
||||
);
|
||||
instance.setState({show: false});
|
||||
expect(instance.state.show).toBe(false);
|
||||
expect(container.innerHTML).toBe('<div><div>parent</div></div>');
|
||||
});
|
||||
|
||||
it('should keep track of namespace across portals (simple)', () => {
|
||||
assertNamespacesMatch(
|
||||
<svg {...expectSVG}>
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
let React;
|
||||
let ReactDOM;
|
||||
|
||||
describe('ReactDOMHooks', () => {
|
||||
describe('ReactDOMSuspensePlaceholder', () => {
|
||||
let container;
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -29,83 +29,6 @@ describe('ReactDOMHooks', () => {
|
||||
document.body.removeChild(container);
|
||||
});
|
||||
|
||||
it('can ReactDOM.render() from useEffect', () => {
|
||||
let container2 = document.createElement('div');
|
||||
let container3 = document.createElement('div');
|
||||
|
||||
function Example1({n}) {
|
||||
React.useEffect(() => {
|
||||
ReactDOM.render(<Example2 n={n} />, container2);
|
||||
});
|
||||
return 1 * n;
|
||||
}
|
||||
|
||||
function Example2({n}) {
|
||||
React.useEffect(() => {
|
||||
ReactDOM.render(<Example3 n={n} />, container3);
|
||||
});
|
||||
return 2 * n;
|
||||
}
|
||||
|
||||
function Example3({n}) {
|
||||
return 3 * n;
|
||||
}
|
||||
|
||||
ReactDOM.render(<Example1 n={1} />, container);
|
||||
expect(container.textContent).toBe('1');
|
||||
expect(container2.textContent).toBe('');
|
||||
expect(container3.textContent).toBe('');
|
||||
jest.runAllTimers();
|
||||
expect(container.textContent).toBe('1');
|
||||
expect(container2.textContent).toBe('2');
|
||||
expect(container3.textContent).toBe('3');
|
||||
|
||||
ReactDOM.render(<Example1 n={2} />, container);
|
||||
expect(container.textContent).toBe('2');
|
||||
expect(container2.textContent).toBe('2'); // Not flushed yet
|
||||
expect(container3.textContent).toBe('3'); // Not flushed yet
|
||||
jest.runAllTimers();
|
||||
expect(container.textContent).toBe('2');
|
||||
expect(container2.textContent).toBe('4');
|
||||
expect(container3.textContent).toBe('6');
|
||||
});
|
||||
|
||||
it('can batch synchronous work inside effects with other work', () => {
|
||||
let otherContainer = document.createElement('div');
|
||||
|
||||
let calledA = false;
|
||||
function A() {
|
||||
calledA = true;
|
||||
return 'A';
|
||||
}
|
||||
|
||||
let calledB = false;
|
||||
function B() {
|
||||
calledB = true;
|
||||
return 'B';
|
||||
}
|
||||
|
||||
let _set;
|
||||
function Foo() {
|
||||
_set = React.useState(0)[1];
|
||||
React.useEffect(() => {
|
||||
ReactDOM.render(<A />, otherContainer);
|
||||
});
|
||||
return null;
|
||||
}
|
||||
|
||||
ReactDOM.render(<Foo />, container);
|
||||
ReactDOM.unstable_batchedUpdates(() => {
|
||||
_set(0); // Forces the effect to be flushed
|
||||
expect(otherContainer.textContent).toBe('');
|
||||
ReactDOM.render(<B />, otherContainer);
|
||||
expect(otherContainer.textContent).toBe('');
|
||||
});
|
||||
expect(otherContainer.textContent).toBe('B');
|
||||
expect(calledA).toBe(false); // It was in a batch
|
||||
expect(calledB).toBe(true);
|
||||
});
|
||||
|
||||
it('should not bail out when an update is scheduled from within an event handler', () => {
|
||||
const {createRef, useCallback, useState} = React;
|
||||
|
||||
|
||||
@@ -410,7 +410,7 @@ describe('ReactDOMRoot', () => {
|
||||
// We care about this warning:
|
||||
'You are calling ReactDOM.hydrate() on a container that was previously ' +
|
||||
'passed to ReactDOM.unstable_createRoot(). This is not supported. ' +
|
||||
'Did you mean to call createRoot(container, {hydrate: true}).render(element)?',
|
||||
'Did you mean to call root.render(element, {hydrate: true})?',
|
||||
// This is more of a symptom but restructuring the code to avoid it isn't worth it:
|
||||
'Replacing React-rendered children with a new root component.',
|
||||
],
|
||||
|
||||
@@ -482,98 +482,5 @@ describe('ReactDOMServerIntegration', () => {
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// Regression test for https://github.com/facebook/react/issues/14705
|
||||
it('does not pollute later renders when stream destroyed', () => {
|
||||
const LoggedInUser = React.createContext('default');
|
||||
|
||||
const AppWithUser = user => (
|
||||
<LoggedInUser.Provider value={user}>
|
||||
<header>
|
||||
<LoggedInUser.Consumer>{whoAmI => whoAmI}</LoggedInUser.Consumer>
|
||||
</header>
|
||||
</LoggedInUser.Provider>
|
||||
);
|
||||
|
||||
const stream = ReactDOMServer.renderToNodeStream(
|
||||
AppWithUser('Amy'),
|
||||
).setEncoding('utf8');
|
||||
|
||||
// This is an implementation detail because we test a memory leak
|
||||
const {threadID} = stream.partialRenderer;
|
||||
|
||||
// Read enough to render Provider but not enough for it to be exited
|
||||
stream._read(10);
|
||||
expect(LoggedInUser[threadID]).toBe('Amy');
|
||||
|
||||
stream.destroy();
|
||||
|
||||
const AppWithUserNoProvider = () => (
|
||||
<LoggedInUser.Consumer>{whoAmI => whoAmI}</LoggedInUser.Consumer>
|
||||
);
|
||||
|
||||
const stream2 = ReactDOMServer.renderToNodeStream(
|
||||
AppWithUserNoProvider(),
|
||||
).setEncoding('utf8');
|
||||
|
||||
// Sanity check to ensure 2nd render has same threadID as 1st render,
|
||||
// otherwise this test is not testing what it's meant to
|
||||
expect(stream2.partialRenderer.threadID).toBe(threadID);
|
||||
|
||||
const markup = stream2.read(Infinity);
|
||||
|
||||
expect(markup).toBe('default');
|
||||
});
|
||||
|
||||
// Regression test for https://github.com/facebook/react/issues/14705
|
||||
it('frees context value reference when stream destroyed', () => {
|
||||
const LoggedInUser = React.createContext('default');
|
||||
|
||||
const AppWithUser = user => (
|
||||
<LoggedInUser.Provider value={user}>
|
||||
<header>
|
||||
<LoggedInUser.Consumer>{whoAmI => whoAmI}</LoggedInUser.Consumer>
|
||||
</header>
|
||||
</LoggedInUser.Provider>
|
||||
);
|
||||
|
||||
const stream = ReactDOMServer.renderToNodeStream(
|
||||
AppWithUser('Amy'),
|
||||
).setEncoding('utf8');
|
||||
|
||||
// This is an implementation detail because we test a memory leak
|
||||
const {threadID} = stream.partialRenderer;
|
||||
|
||||
// Read enough to render Provider but not enough for it to be exited
|
||||
stream._read(10);
|
||||
expect(LoggedInUser[threadID]).toBe('Amy');
|
||||
|
||||
stream.destroy();
|
||||
expect(LoggedInUser[threadID]).toBe('default');
|
||||
});
|
||||
|
||||
it('does not pollute sync renders after an error', () => {
|
||||
const LoggedInUser = React.createContext('default');
|
||||
const Crash = () => {
|
||||
throw new Error('Boo!');
|
||||
};
|
||||
const AppWithUser = user => (
|
||||
<LoggedInUser.Provider value={user}>
|
||||
<LoggedInUser.Consumer>{whoAmI => whoAmI}</LoggedInUser.Consumer>
|
||||
<Crash />
|
||||
</LoggedInUser.Provider>
|
||||
);
|
||||
|
||||
expect(() => {
|
||||
ReactDOMServer.renderToString(AppWithUser('Casper'));
|
||||
}).toThrow('Boo');
|
||||
|
||||
// Should not report a value from failed render
|
||||
expect(
|
||||
ReactDOMServer.renderToString(
|
||||
<LoggedInUser.Consumer>{whoAmI => whoAmI}</LoggedInUser.Consumer>,
|
||||
),
|
||||
).toBe('default');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,637 +0,0 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @emails react-core
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
let React;
|
||||
let ReactDOM;
|
||||
let ReactDOMServer;
|
||||
let ReactFeatureFlags;
|
||||
let Suspense;
|
||||
let act;
|
||||
|
||||
describe('ReactDOMServerPartialHydration', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetModuleRegistry();
|
||||
|
||||
ReactFeatureFlags = require('shared/ReactFeatureFlags');
|
||||
ReactFeatureFlags.enableSuspenseServerRenderer = true;
|
||||
|
||||
React = require('react');
|
||||
ReactDOM = require('react-dom');
|
||||
act = require('react-dom/test-utils').act;
|
||||
ReactDOMServer = require('react-dom/server');
|
||||
Suspense = React.Suspense;
|
||||
});
|
||||
|
||||
it('hydrates a parent even if a child Suspense boundary is blocked', async () => {
|
||||
let suspend = false;
|
||||
let resolve;
|
||||
let promise = new Promise(resolvePromise => (resolve = resolvePromise));
|
||||
let ref = React.createRef();
|
||||
|
||||
function Child() {
|
||||
if (suspend) {
|
||||
throw promise;
|
||||
} else {
|
||||
return 'Hello';
|
||||
}
|
||||
}
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<div>
|
||||
<Suspense fallback="Loading...">
|
||||
<span ref={ref}>
|
||||
<Child />
|
||||
</span>
|
||||
</Suspense>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// First we render the final HTML. With the streaming renderer
|
||||
// this may have suspense points on the server but here we want
|
||||
// to test the completed HTML. Don't suspend on the server.
|
||||
suspend = false;
|
||||
let finalHTML = ReactDOMServer.renderToString(<App />);
|
||||
|
||||
let container = document.createElement('div');
|
||||
container.innerHTML = finalHTML;
|
||||
|
||||
let span = container.getElementsByTagName('span')[0];
|
||||
|
||||
// On the client we don't have all data yet but we want to start
|
||||
// hydrating anyway.
|
||||
suspend = true;
|
||||
let root = ReactDOM.unstable_createRoot(container, {hydrate: true});
|
||||
root.render(<App />);
|
||||
jest.runAllTimers();
|
||||
|
||||
expect(ref.current).toBe(null);
|
||||
|
||||
// Resolving the promise should continue hydration
|
||||
suspend = false;
|
||||
resolve();
|
||||
await promise;
|
||||
jest.runAllTimers();
|
||||
|
||||
// We should now have hydrated with a ref on the existing span.
|
||||
expect(ref.current).toBe(span);
|
||||
});
|
||||
|
||||
it('can insert siblings before the dehydrated boundary', () => {
|
||||
let suspend = false;
|
||||
let promise = new Promise(() => {});
|
||||
let showSibling;
|
||||
|
||||
function Child() {
|
||||
if (suspend) {
|
||||
throw promise;
|
||||
} else {
|
||||
return 'Second';
|
||||
}
|
||||
}
|
||||
|
||||
function Sibling() {
|
||||
let [visible, setVisibilty] = React.useState(false);
|
||||
showSibling = () => setVisibilty(true);
|
||||
if (visible) {
|
||||
return <div>First</div>;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<div>
|
||||
<Sibling />
|
||||
<Suspense fallback="Loading...">
|
||||
<span>
|
||||
<Child />
|
||||
</span>
|
||||
</Suspense>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
suspend = false;
|
||||
let finalHTML = ReactDOMServer.renderToString(<App />);
|
||||
let container = document.createElement('div');
|
||||
container.innerHTML = finalHTML;
|
||||
|
||||
// On the client we don't have all data yet but we want to start
|
||||
// hydrating anyway.
|
||||
suspend = true;
|
||||
|
||||
act(() => {
|
||||
ReactDOM.hydrate(<App />, container);
|
||||
});
|
||||
|
||||
expect(container.firstChild.firstChild.tagName).not.toBe('DIV');
|
||||
|
||||
// In this state, we can still update the siblings.
|
||||
act(() => showSibling());
|
||||
|
||||
expect(container.firstChild.firstChild.tagName).toBe('DIV');
|
||||
expect(container.firstChild.firstChild.textContent).toBe('First');
|
||||
});
|
||||
|
||||
it('can delete the dehydrated boundary before it is hydrated', () => {
|
||||
let suspend = false;
|
||||
let promise = new Promise(() => {});
|
||||
let hideMiddle;
|
||||
|
||||
function Child() {
|
||||
if (suspend) {
|
||||
throw promise;
|
||||
} else {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div>Middle</div>
|
||||
Some text
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function App() {
|
||||
let [visible, setVisibilty] = React.useState(true);
|
||||
hideMiddle = () => setVisibilty(false);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div>Before</div>
|
||||
{visible ? (
|
||||
<Suspense fallback="Loading...">
|
||||
<Child />
|
||||
</Suspense>
|
||||
) : null}
|
||||
<div>After</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
suspend = false;
|
||||
let finalHTML = ReactDOMServer.renderToString(<App />);
|
||||
let container = document.createElement('div');
|
||||
container.innerHTML = finalHTML;
|
||||
|
||||
// On the client we don't have all data yet but we want to start
|
||||
// hydrating anyway.
|
||||
suspend = true;
|
||||
act(() => {
|
||||
ReactDOM.hydrate(<App />, container);
|
||||
});
|
||||
|
||||
expect(container.firstChild.children[1].textContent).toBe('Middle');
|
||||
|
||||
// In this state, we can still delete the boundary.
|
||||
act(() => hideMiddle());
|
||||
|
||||
expect(container.firstChild.children[1].textContent).toBe('After');
|
||||
});
|
||||
|
||||
it('regenerates the content if props have changed before hydration completes', async () => {
|
||||
let suspend = false;
|
||||
let resolve;
|
||||
let promise = new Promise(resolvePromise => (resolve = resolvePromise));
|
||||
let ref = React.createRef();
|
||||
|
||||
function Child({text}) {
|
||||
if (suspend) {
|
||||
throw promise;
|
||||
} else {
|
||||
return text;
|
||||
}
|
||||
}
|
||||
|
||||
function App({text, className}) {
|
||||
return (
|
||||
<div>
|
||||
<Suspense fallback="Loading...">
|
||||
<span ref={ref} className={className}>
|
||||
<Child text={text} />
|
||||
</span>
|
||||
</Suspense>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
suspend = false;
|
||||
let finalHTML = ReactDOMServer.renderToString(
|
||||
<App text="Hello" className="hello" />,
|
||||
);
|
||||
let container = document.createElement('div');
|
||||
container.innerHTML = finalHTML;
|
||||
|
||||
let span = container.getElementsByTagName('span')[0];
|
||||
|
||||
// On the client we don't have all data yet but we want to start
|
||||
// hydrating anyway.
|
||||
suspend = true;
|
||||
let root = ReactDOM.unstable_createRoot(container, {hydrate: true});
|
||||
root.render(<App text="Hello" className="hello" />);
|
||||
jest.runAllTimers();
|
||||
|
||||
expect(ref.current).toBe(null);
|
||||
expect(span.textContent).toBe('Hello');
|
||||
|
||||
// Render an update, which will be higher or the same priority as pinging the hydration.
|
||||
root.render(<App text="Hi" className="hi" />);
|
||||
|
||||
// At the same time, resolving the promise so that rendering can complete.
|
||||
suspend = false;
|
||||
resolve();
|
||||
await promise;
|
||||
|
||||
// Flushing both of these in the same batch won't be able to hydrate so we'll
|
||||
// probably throw away the existing subtree.
|
||||
jest.runAllTimers();
|
||||
|
||||
// Pick up the new span. In an ideal implementation this might be the same span
|
||||
// but patched up. At the time of writing, this will be a new span though.
|
||||
span = container.getElementsByTagName('span')[0];
|
||||
|
||||
// We should now have fully rendered with a ref on the new span.
|
||||
expect(ref.current).toBe(span);
|
||||
expect(span.textContent).toBe('Hi');
|
||||
// If we ended up hydrating the existing content, we won't have properly
|
||||
// patched up the tree, which might mean we haven't patched the className.
|
||||
expect(span.className).toBe('hi');
|
||||
});
|
||||
|
||||
it('shows the fallback if props have changed before hydration completes and is still suspended', async () => {
|
||||
let suspend = false;
|
||||
let resolve;
|
||||
let promise = new Promise(resolvePromise => (resolve = resolvePromise));
|
||||
let ref = React.createRef();
|
||||
|
||||
function Child({text}) {
|
||||
if (suspend) {
|
||||
throw promise;
|
||||
} else {
|
||||
return text;
|
||||
}
|
||||
}
|
||||
|
||||
function App({text, className}) {
|
||||
return (
|
||||
<div>
|
||||
<Suspense fallback="Loading...">
|
||||
<span ref={ref} className={className}>
|
||||
<Child text={text} />
|
||||
</span>
|
||||
</Suspense>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
suspend = false;
|
||||
let finalHTML = ReactDOMServer.renderToString(
|
||||
<App text="Hello" className="hello" />,
|
||||
);
|
||||
let container = document.createElement('div');
|
||||
container.innerHTML = finalHTML;
|
||||
|
||||
// On the client we don't have all data yet but we want to start
|
||||
// hydrating anyway.
|
||||
suspend = true;
|
||||
let root = ReactDOM.unstable_createRoot(container, {hydrate: true});
|
||||
root.render(<App text="Hello" className="hello" />);
|
||||
jest.runAllTimers();
|
||||
|
||||
expect(ref.current).toBe(null);
|
||||
|
||||
// Render an update, but leave it still suspended.
|
||||
root.render(<App text="Hi" className="hi" />);
|
||||
|
||||
// Flushing now should delete the existing content and show the fallback.
|
||||
jest.runAllTimers();
|
||||
|
||||
expect(container.getElementsByTagName('span').length).toBe(0);
|
||||
expect(ref.current).toBe(null);
|
||||
expect(container.textContent).toBe('Loading...');
|
||||
|
||||
// Unsuspending shows the content.
|
||||
suspend = false;
|
||||
resolve();
|
||||
await promise;
|
||||
|
||||
jest.runAllTimers();
|
||||
|
||||
let span = container.getElementsByTagName('span')[0];
|
||||
expect(span.textContent).toBe('Hi');
|
||||
expect(span.className).toBe('hi');
|
||||
expect(ref.current).toBe(span);
|
||||
expect(container.textContent).toBe('Hi');
|
||||
});
|
||||
|
||||
it('shows the fallback of the outer if fallback is missing', async () => {
|
||||
// This is the same exact test as above but with a nested Suspense without a fallback.
|
||||
// This should be a noop.
|
||||
let suspend = false;
|
||||
let resolve;
|
||||
let promise = new Promise(resolvePromise => (resolve = resolvePromise));
|
||||
let ref = React.createRef();
|
||||
|
||||
function Child({text}) {
|
||||
if (suspend) {
|
||||
throw promise;
|
||||
} else {
|
||||
return text;
|
||||
}
|
||||
}
|
||||
|
||||
function App({text, className}) {
|
||||
return (
|
||||
<div>
|
||||
<Suspense fallback="Loading...">
|
||||
<span ref={ref} className={className}>
|
||||
<Suspense maxDuration={200}>
|
||||
<Child text={text} />
|
||||
</Suspense>
|
||||
</span>
|
||||
</Suspense>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
suspend = false;
|
||||
let finalHTML = ReactDOMServer.renderToString(
|
||||
<App text="Hello" className="hello" />,
|
||||
);
|
||||
let container = document.createElement('div');
|
||||
container.innerHTML = finalHTML;
|
||||
|
||||
// On the client we don't have all data yet but we want to start
|
||||
// hydrating anyway.
|
||||
suspend = true;
|
||||
let root = ReactDOM.unstable_createRoot(container, {hydrate: true});
|
||||
root.render(<App text="Hello" className="hello" />);
|
||||
jest.runAllTimers();
|
||||
|
||||
expect(ref.current).toBe(null);
|
||||
|
||||
// Render an update, but leave it still suspended.
|
||||
root.render(<App text="Hi" className="hi" />);
|
||||
|
||||
// Flushing now should delete the existing content and show the fallback.
|
||||
jest.runAllTimers();
|
||||
|
||||
expect(container.getElementsByTagName('span').length).toBe(0);
|
||||
expect(ref.current).toBe(null);
|
||||
expect(container.textContent).toBe('Loading...');
|
||||
|
||||
// Unsuspending shows the content.
|
||||
suspend = false;
|
||||
resolve();
|
||||
await promise;
|
||||
|
||||
jest.runAllTimers();
|
||||
|
||||
let span = container.getElementsByTagName('span')[0];
|
||||
expect(span.textContent).toBe('Hi');
|
||||
expect(span.className).toBe('hi');
|
||||
expect(ref.current).toBe(span);
|
||||
expect(container.textContent).toBe('Hi');
|
||||
});
|
||||
|
||||
it('clears nested suspense boundaries if they did not hydrate yet', async () => {
|
||||
let suspend = false;
|
||||
let resolve;
|
||||
let promise = new Promise(resolvePromise => (resolve = resolvePromise));
|
||||
let ref = React.createRef();
|
||||
|
||||
function Child({text}) {
|
||||
if (suspend) {
|
||||
throw promise;
|
||||
} else {
|
||||
return text;
|
||||
}
|
||||
}
|
||||
|
||||
function App({text, className}) {
|
||||
return (
|
||||
<div>
|
||||
<Suspense fallback="Loading...">
|
||||
<Suspense fallback="Never happens">
|
||||
<Child text={text} />
|
||||
</Suspense>{' '}
|
||||
<span ref={ref} className={className}>
|
||||
<Child text={text} />
|
||||
</span>
|
||||
</Suspense>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
suspend = false;
|
||||
let finalHTML = ReactDOMServer.renderToString(
|
||||
<App text="Hello" className="hello" />,
|
||||
);
|
||||
let container = document.createElement('div');
|
||||
container.innerHTML = finalHTML;
|
||||
|
||||
// On the client we don't have all data yet but we want to start
|
||||
// hydrating anyway.
|
||||
suspend = true;
|
||||
let root = ReactDOM.unstable_createRoot(container, {hydrate: true});
|
||||
root.render(<App text="Hello" className="hello" />);
|
||||
jest.runAllTimers();
|
||||
|
||||
expect(ref.current).toBe(null);
|
||||
|
||||
// Render an update, but leave it still suspended.
|
||||
root.render(<App text="Hi" className="hi" />);
|
||||
|
||||
// Flushing now should delete the existing content and show the fallback.
|
||||
jest.runAllTimers();
|
||||
|
||||
expect(container.getElementsByTagName('span').length).toBe(0);
|
||||
expect(ref.current).toBe(null);
|
||||
expect(container.textContent).toBe('Loading...');
|
||||
|
||||
// Unsuspending shows the content.
|
||||
suspend = false;
|
||||
resolve();
|
||||
await promise;
|
||||
|
||||
jest.runAllTimers();
|
||||
|
||||
let span = container.getElementsByTagName('span')[0];
|
||||
expect(span.textContent).toBe('Hi');
|
||||
expect(span.className).toBe('hi');
|
||||
expect(ref.current).toBe(span);
|
||||
expect(container.textContent).toBe('Hi Hi');
|
||||
});
|
||||
|
||||
it('regenerates the content if context has changed before hydration completes', async () => {
|
||||
let suspend = false;
|
||||
let resolve;
|
||||
let promise = new Promise(resolvePromise => (resolve = resolvePromise));
|
||||
let ref = React.createRef();
|
||||
let Context = React.createContext(null);
|
||||
|
||||
function Child() {
|
||||
let {text, className} = React.useContext(Context);
|
||||
if (suspend) {
|
||||
throw promise;
|
||||
} else {
|
||||
return (
|
||||
<span ref={ref} className={className}>
|
||||
{text}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const App = React.memo(function App() {
|
||||
return (
|
||||
<div>
|
||||
<Suspense fallback="Loading...">
|
||||
<Child />
|
||||
</Suspense>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
suspend = false;
|
||||
let finalHTML = ReactDOMServer.renderToString(
|
||||
<Context.Provider value={{text: 'Hello', className: 'hello'}}>
|
||||
<App />
|
||||
</Context.Provider>,
|
||||
);
|
||||
let container = document.createElement('div');
|
||||
container.innerHTML = finalHTML;
|
||||
|
||||
let span = container.getElementsByTagName('span')[0];
|
||||
|
||||
// On the client we don't have all data yet but we want to start
|
||||
// hydrating anyway.
|
||||
suspend = true;
|
||||
let root = ReactDOM.unstable_createRoot(container, {hydrate: true});
|
||||
root.render(
|
||||
<Context.Provider value={{text: 'Hello', className: 'hello'}}>
|
||||
<App />
|
||||
</Context.Provider>,
|
||||
);
|
||||
jest.runAllTimers();
|
||||
|
||||
expect(ref.current).toBe(null);
|
||||
expect(span.textContent).toBe('Hello');
|
||||
|
||||
// Render an update, which will be higher or the same priority as pinging the hydration.
|
||||
root.render(
|
||||
<Context.Provider value={{text: 'Hi', className: 'hi'}}>
|
||||
<App />
|
||||
</Context.Provider>,
|
||||
);
|
||||
|
||||
// At the same time, resolving the promise so that rendering can complete.
|
||||
suspend = false;
|
||||
resolve();
|
||||
await promise;
|
||||
|
||||
// Flushing both of these in the same batch won't be able to hydrate so we'll
|
||||
// probably throw away the existing subtree.
|
||||
jest.runAllTimers();
|
||||
|
||||
// Pick up the new span. In an ideal implementation this might be the same span
|
||||
// but patched up. At the time of writing, this will be a new span though.
|
||||
span = container.getElementsByTagName('span')[0];
|
||||
|
||||
// We should now have fully rendered with a ref on the new span.
|
||||
expect(ref.current).toBe(span);
|
||||
expect(span.textContent).toBe('Hi');
|
||||
// If we ended up hydrating the existing content, we won't have properly
|
||||
// patched up the tree, which might mean we haven't patched the className.
|
||||
expect(span.className).toBe('hi');
|
||||
});
|
||||
|
||||
it('shows the fallback if context has changed before hydration completes and is still suspended', async () => {
|
||||
let suspend = false;
|
||||
let resolve;
|
||||
let promise = new Promise(resolvePromise => (resolve = resolvePromise));
|
||||
let ref = React.createRef();
|
||||
let Context = React.createContext(null);
|
||||
|
||||
function Child() {
|
||||
let {text, className} = React.useContext(Context);
|
||||
if (suspend) {
|
||||
throw promise;
|
||||
} else {
|
||||
return (
|
||||
<span ref={ref} className={className}>
|
||||
{text}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const App = React.memo(function App() {
|
||||
return (
|
||||
<div>
|
||||
<Suspense fallback="Loading...">
|
||||
<Child />
|
||||
</Suspense>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
suspend = false;
|
||||
let finalHTML = ReactDOMServer.renderToString(
|
||||
<Context.Provider value={{text: 'Hello', className: 'hello'}}>
|
||||
<App />
|
||||
</Context.Provider>,
|
||||
);
|
||||
let container = document.createElement('div');
|
||||
container.innerHTML = finalHTML;
|
||||
|
||||
// On the client we don't have all data yet but we want to start
|
||||
// hydrating anyway.
|
||||
suspend = true;
|
||||
let root = ReactDOM.unstable_createRoot(container, {hydrate: true});
|
||||
root.render(
|
||||
<Context.Provider value={{text: 'Hello', className: 'hello'}}>
|
||||
<App />
|
||||
</Context.Provider>,
|
||||
);
|
||||
jest.runAllTimers();
|
||||
|
||||
expect(ref.current).toBe(null);
|
||||
|
||||
// Render an update, but leave it still suspended.
|
||||
root.render(
|
||||
<Context.Provider value={{text: 'Hi', className: 'hi'}}>
|
||||
<App />
|
||||
</Context.Provider>,
|
||||
);
|
||||
|
||||
// Flushing now should delete the existing content and show the fallback.
|
||||
jest.runAllTimers();
|
||||
|
||||
expect(container.getElementsByTagName('span').length).toBe(0);
|
||||
expect(ref.current).toBe(null);
|
||||
expect(container.textContent).toBe('Loading...');
|
||||
|
||||
// Unsuspending shows the content.
|
||||
suspend = false;
|
||||
resolve();
|
||||
await promise;
|
||||
|
||||
jest.runAllTimers();
|
||||
|
||||
let span = container.getElementsByTagName('span')[0];
|
||||
expect(span.textContent).toBe('Hi');
|
||||
expect(span.className).toBe('hi');
|
||||
expect(ref.current).toBe(span);
|
||||
expect(container.textContent).toBe('Hi');
|
||||
});
|
||||
});
|
||||
@@ -52,47 +52,38 @@ describe('ReactDOMServerSuspense', () => {
|
||||
}
|
||||
|
||||
it('should render the children when no promise is thrown', async () => {
|
||||
const c = await serverRender(
|
||||
<div>
|
||||
<React.Suspense fallback={<Text text="Fallback" />}>
|
||||
<Text text="Children" />
|
||||
</React.Suspense>
|
||||
</div>,
|
||||
const e = await serverRender(
|
||||
<React.Suspense fallback={<Text text="Fallback" />}>
|
||||
<Text text="Children" />
|
||||
</React.Suspense>,
|
||||
);
|
||||
const e = c.children[0];
|
||||
|
||||
expect(e.tagName).toBe('DIV');
|
||||
expect(e.textContent).toBe('Children');
|
||||
});
|
||||
|
||||
it('should render the fallback when a promise thrown', async () => {
|
||||
const c = await serverRender(
|
||||
<div>
|
||||
<React.Suspense fallback={<Text text="Fallback" />}>
|
||||
<AsyncText text="Children" />
|
||||
</React.Suspense>
|
||||
</div>,
|
||||
const e = await serverRender(
|
||||
<React.Suspense fallback={<Text text="Fallback" />}>
|
||||
<AsyncText text="Children" />
|
||||
</React.Suspense>,
|
||||
);
|
||||
const e = c.children[0];
|
||||
|
||||
expect(e.tagName).toBe('DIV');
|
||||
expect(e.textContent).toBe('Fallback');
|
||||
});
|
||||
|
||||
it('should work with nested suspense components', async () => {
|
||||
const c = await serverRender(
|
||||
<div>
|
||||
<React.Suspense fallback={<Text text="Fallback" />}>
|
||||
<div>
|
||||
<Text text="Children" />
|
||||
<React.Suspense fallback={<Text text="Fallback" />}>
|
||||
<AsyncText text="Children" />
|
||||
</React.Suspense>
|
||||
</div>
|
||||
</React.Suspense>
|
||||
</div>,
|
||||
const e = await serverRender(
|
||||
<React.Suspense fallback={<Text text="Fallback" />}>
|
||||
<div>
|
||||
<Text text="Children" />
|
||||
<React.Suspense fallback={<Text text="Fallback" />}>
|
||||
<AsyncText text="Children" />
|
||||
</React.Suspense>
|
||||
</div>
|
||||
</React.Suspense>,
|
||||
);
|
||||
const e = c.children[0];
|
||||
|
||||
expect(e.innerHTML).toBe('<div>Children</div><div>Fallback</div>');
|
||||
});
|
||||
|
||||
@@ -634,9 +634,10 @@ describe('ReactTestUtils', () => {
|
||||
button.dispatchEvent(new MouseEvent('click', {bubbles: true}));
|
||||
});
|
||||
expect(button.innerHTML).toBe('2');
|
||||
expect(() => setValueRef(1)).toWarnDev([
|
||||
'An update to App inside a test was not wrapped in act(...).',
|
||||
]);
|
||||
expect(() => setValueRef(1)).toWarnDev(
|
||||
['An update to App inside a test was not wrapped in act(...).'],
|
||||
{withoutStack: 1},
|
||||
);
|
||||
document.body.removeChild(container);
|
||||
});
|
||||
|
||||
|
||||
2
packages/react-dom/src/client/ReactDOM.js
vendored
2
packages/react-dom/src/client/ReactDOM.js
vendored
@@ -656,7 +656,7 @@ const ReactDOM: Object = {
|
||||
!container._reactHasBeenPassedToCreateRootDEV,
|
||||
'You are calling ReactDOM.hydrate() on a container that was previously ' +
|
||||
'passed to ReactDOM.%s(). This is not supported. ' +
|
||||
'Did you mean to call createRoot(container, {hydrate: true}).render(element)?',
|
||||
'Did you mean to call root.render(element, {hydrate: true})?',
|
||||
enableStableConcurrentModeAPIs ? 'createRoot' : 'unstable_createRoot',
|
||||
);
|
||||
}
|
||||
|
||||
142
packages/react-dom/src/client/ReactDOMHostConfig.js
vendored
142
packages/react-dom/src/client/ReactDOMHostConfig.js
vendored
@@ -56,8 +56,7 @@ export type Props = {
|
||||
export type Container = Element | Document;
|
||||
export type Instance = Element;
|
||||
export type TextInstance = Text;
|
||||
export type SuspenseInstance = Comment;
|
||||
export type HydratableInstance = Instance | TextInstance | SuspenseInstance;
|
||||
export type HydratableInstance = Element | Text;
|
||||
export type PublicInstance = Element | Text;
|
||||
type HostContextDev = {
|
||||
namespace: string,
|
||||
@@ -74,7 +73,6 @@ import {
|
||||
unstable_scheduleCallback as scheduleDeferredCallback,
|
||||
unstable_cancelCallback as cancelDeferredCallback,
|
||||
} from 'scheduler';
|
||||
import {enableSuspenseServerRenderer} from 'shared/ReactFeatureFlags';
|
||||
export {
|
||||
unstable_now as now,
|
||||
unstable_scheduleCallback as scheduleDeferredCallback,
|
||||
@@ -87,9 +85,6 @@ if (__DEV__) {
|
||||
SUPPRESS_HYDRATION_WARNING = 'suppressHydrationWarning';
|
||||
}
|
||||
|
||||
const SUSPENSE_START_DATA = '$';
|
||||
const SUSPENSE_END_DATA = '/$';
|
||||
|
||||
const STYLE = 'style';
|
||||
|
||||
let eventsEnabled: ?boolean = null;
|
||||
@@ -402,7 +397,7 @@ export function appendChildToContainer(
|
||||
export function insertBefore(
|
||||
parentInstance: Instance,
|
||||
child: Instance | TextInstance,
|
||||
beforeChild: Instance | TextInstance | SuspenseInstance,
|
||||
beforeChild: Instance | TextInstance,
|
||||
): void {
|
||||
parentInstance.insertBefore(child, beforeChild);
|
||||
}
|
||||
@@ -410,7 +405,7 @@ export function insertBefore(
|
||||
export function insertInContainerBefore(
|
||||
container: Container,
|
||||
child: Instance | TextInstance,
|
||||
beforeChild: Instance | TextInstance | SuspenseInstance,
|
||||
beforeChild: Instance | TextInstance,
|
||||
): void {
|
||||
if (container.nodeType === COMMENT_NODE) {
|
||||
(container.parentNode: any).insertBefore(child, beforeChild);
|
||||
@@ -421,14 +416,14 @@ export function insertInContainerBefore(
|
||||
|
||||
export function removeChild(
|
||||
parentInstance: Instance,
|
||||
child: Instance | TextInstance | SuspenseInstance,
|
||||
child: Instance | TextInstance,
|
||||
): void {
|
||||
parentInstance.removeChild(child);
|
||||
}
|
||||
|
||||
export function removeChildFromContainer(
|
||||
container: Container,
|
||||
child: Instance | TextInstance | SuspenseInstance,
|
||||
child: Instance | TextInstance,
|
||||
): void {
|
||||
if (container.nodeType === COMMENT_NODE) {
|
||||
(container.parentNode: any).removeChild(child);
|
||||
@@ -437,49 +432,6 @@ export function removeChildFromContainer(
|
||||
}
|
||||
}
|
||||
|
||||
export function clearSuspenseBoundary(
|
||||
parentInstance: Instance,
|
||||
suspenseInstance: SuspenseInstance,
|
||||
): void {
|
||||
let node = suspenseInstance;
|
||||
// Delete all nodes within this suspense boundary.
|
||||
// There might be nested nodes so we need to keep track of how
|
||||
// deep we are and only break out when we're back on top.
|
||||
let depth = 0;
|
||||
do {
|
||||
let nextNode = node.nextSibling;
|
||||
parentInstance.removeChild(node);
|
||||
if (nextNode && nextNode.nodeType === COMMENT_NODE) {
|
||||
let data = ((nextNode: any).data: string);
|
||||
if (data === SUSPENSE_END_DATA) {
|
||||
if (depth === 0) {
|
||||
parentInstance.removeChild(nextNode);
|
||||
return;
|
||||
} else {
|
||||
depth--;
|
||||
}
|
||||
} else if (data === SUSPENSE_START_DATA) {
|
||||
depth++;
|
||||
}
|
||||
}
|
||||
node = nextNode;
|
||||
} while (node);
|
||||
// TODO: Warn, we didn't find the end comment boundary.
|
||||
}
|
||||
|
||||
export function clearSuspenseBoundaryFromContainer(
|
||||
container: Container,
|
||||
suspenseInstance: SuspenseInstance,
|
||||
): void {
|
||||
if (container.nodeType === COMMENT_NODE) {
|
||||
clearSuspenseBoundary((container.parentNode: any), suspenseInstance);
|
||||
} else if (container.nodeType === ELEMENT_NODE) {
|
||||
clearSuspenseBoundary((container: any), suspenseInstance);
|
||||
} else {
|
||||
// Document nodes should never contain suspense boundaries.
|
||||
}
|
||||
}
|
||||
|
||||
export function hideInstance(instance: Instance): void {
|
||||
// TODO: Does this work for all element types? What about MathML? Should we
|
||||
// pass host context to this method?
|
||||
@@ -517,7 +469,7 @@ export function unhideTextInstance(
|
||||
export const supportsHydration = true;
|
||||
|
||||
export function canHydrateInstance(
|
||||
instance: HydratableInstance,
|
||||
instance: Instance | TextInstance,
|
||||
type: string,
|
||||
props: Props,
|
||||
): null | Instance {
|
||||
@@ -532,7 +484,7 @@ export function canHydrateInstance(
|
||||
}
|
||||
|
||||
export function canHydrateTextInstance(
|
||||
instance: HydratableInstance,
|
||||
instance: Instance | TextInstance,
|
||||
text: string,
|
||||
): null | TextInstance {
|
||||
if (text === '' || instance.nodeType !== TEXT_NODE) {
|
||||
@@ -543,29 +495,15 @@ export function canHydrateTextInstance(
|
||||
return ((instance: any): TextInstance);
|
||||
}
|
||||
|
||||
export function canHydrateSuspenseInstance(
|
||||
instance: HydratableInstance,
|
||||
): null | SuspenseInstance {
|
||||
if (instance.nodeType !== COMMENT_NODE) {
|
||||
// Empty strings are not parsed by HTML so there won't be a correct match here.
|
||||
return null;
|
||||
}
|
||||
// This has now been refined to a suspense node.
|
||||
return ((instance: any): SuspenseInstance);
|
||||
}
|
||||
|
||||
export function getNextHydratableSibling(
|
||||
instance: HydratableInstance,
|
||||
): null | HydratableInstance {
|
||||
instance: Instance | TextInstance,
|
||||
): null | Instance | TextInstance {
|
||||
let node = instance.nextSibling;
|
||||
// Skip non-hydratable nodes.
|
||||
while (
|
||||
node &&
|
||||
node.nodeType !== ELEMENT_NODE &&
|
||||
node.nodeType !== TEXT_NODE &&
|
||||
(!enableSuspenseServerRenderer ||
|
||||
node.nodeType !== COMMENT_NODE ||
|
||||
(node: any).data !== SUSPENSE_START_DATA)
|
||||
node.nodeType !== TEXT_NODE
|
||||
) {
|
||||
node = node.nextSibling;
|
||||
}
|
||||
@@ -574,16 +512,13 @@ export function getNextHydratableSibling(
|
||||
|
||||
export function getFirstHydratableChild(
|
||||
parentInstance: Container | Instance,
|
||||
): null | HydratableInstance {
|
||||
): null | Instance | TextInstance {
|
||||
let next = parentInstance.firstChild;
|
||||
// Skip non-hydratable nodes.
|
||||
while (
|
||||
next &&
|
||||
next.nodeType !== ELEMENT_NODE &&
|
||||
next.nodeType !== TEXT_NODE &&
|
||||
(!enableSuspenseServerRenderer ||
|
||||
next.nodeType !== COMMENT_NODE ||
|
||||
(next: any).data !== SUSPENSE_START_DATA)
|
||||
next.nodeType !== TEXT_NODE
|
||||
) {
|
||||
next = next.nextSibling;
|
||||
}
|
||||
@@ -627,33 +562,6 @@ export function hydrateTextInstance(
|
||||
return diffHydratedText(textInstance, text);
|
||||
}
|
||||
|
||||
export function getNextHydratableInstanceAfterSuspenseInstance(
|
||||
suspenseInstance: SuspenseInstance,
|
||||
): null | HydratableInstance {
|
||||
let node = suspenseInstance.nextSibling;
|
||||
// Skip past all nodes within this suspense boundary.
|
||||
// There might be nested nodes so we need to keep track of how
|
||||
// deep we are and only break out when we're back on top.
|
||||
let depth = 0;
|
||||
while (node) {
|
||||
if (node.nodeType === COMMENT_NODE) {
|
||||
let data = ((node: any).data: string);
|
||||
if (data === SUSPENSE_END_DATA) {
|
||||
if (depth === 0) {
|
||||
return getNextHydratableSibling((node: any));
|
||||
} else {
|
||||
depth--;
|
||||
}
|
||||
} else if (data === SUSPENSE_START_DATA) {
|
||||
depth++;
|
||||
}
|
||||
}
|
||||
node = node.nextSibling;
|
||||
}
|
||||
// TODO: Warn, we didn't find the end comment boundary.
|
||||
return null;
|
||||
}
|
||||
|
||||
export function didNotMatchHydratedContainerTextInstance(
|
||||
parentContainer: Container,
|
||||
textInstance: TextInstance,
|
||||
@@ -678,13 +586,11 @@ export function didNotMatchHydratedTextInstance(
|
||||
|
||||
export function didNotHydrateContainerInstance(
|
||||
parentContainer: Container,
|
||||
instance: HydratableInstance,
|
||||
instance: Instance | TextInstance,
|
||||
) {
|
||||
if (__DEV__) {
|
||||
if (instance.nodeType === ELEMENT_NODE) {
|
||||
warnForDeletedHydratableElement(parentContainer, (instance: any));
|
||||
} else if (instance.nodeType === COMMENT_NODE) {
|
||||
// TODO: warnForDeletedHydratableSuspenseBoundary
|
||||
} else {
|
||||
warnForDeletedHydratableText(parentContainer, (instance: any));
|
||||
}
|
||||
@@ -695,13 +601,11 @@ export function didNotHydrateInstance(
|
||||
parentType: string,
|
||||
parentProps: Props,
|
||||
parentInstance: Instance,
|
||||
instance: HydratableInstance,
|
||||
instance: Instance | TextInstance,
|
||||
) {
|
||||
if (__DEV__ && parentProps[SUPPRESS_HYDRATION_WARNING] !== true) {
|
||||
if (instance.nodeType === ELEMENT_NODE) {
|
||||
warnForDeletedHydratableElement(parentInstance, (instance: any));
|
||||
} else if (instance.nodeType === COMMENT_NODE) {
|
||||
// TODO: warnForDeletedHydratableSuspenseBoundary
|
||||
} else {
|
||||
warnForDeletedHydratableText(parentInstance, (instance: any));
|
||||
}
|
||||
@@ -727,14 +631,6 @@ export function didNotFindHydratableContainerTextInstance(
|
||||
}
|
||||
}
|
||||
|
||||
export function didNotFindHydratableContainerSuspenseInstance(
|
||||
parentContainer: Container,
|
||||
) {
|
||||
if (__DEV__) {
|
||||
// TODO: warnForInsertedHydratedSupsense(parentContainer);
|
||||
}
|
||||
}
|
||||
|
||||
export function didNotFindHydratableInstance(
|
||||
parentType: string,
|
||||
parentProps: Props,
|
||||
@@ -757,13 +653,3 @@ export function didNotFindHydratableTextInstance(
|
||||
warnForInsertedHydratedText(parentInstance, text);
|
||||
}
|
||||
}
|
||||
|
||||
export function didNotFindHydratableSuspenseInstance(
|
||||
parentType: string,
|
||||
parentProps: Props,
|
||||
parentInstance: Instance,
|
||||
) {
|
||||
if (__DEV__ && parentProps[SUPPRESS_HYDRATION_WARNING] !== true) {
|
||||
// TODO: warnForInsertedHydratedSuspense(parentInstance);
|
||||
}
|
||||
}
|
||||
|
||||
2
packages/react-dom/src/fire/ReactFire.js
vendored
2
packages/react-dom/src/fire/ReactFire.js
vendored
@@ -661,7 +661,7 @@ const ReactDOM: Object = {
|
||||
!container._reactHasBeenPassedToCreateRootDEV,
|
||||
'You are calling ReactDOM.hydrate() on a container that was previously ' +
|
||||
'passed to ReactDOM.%s(). This is not supported. ' +
|
||||
'Did you mean to call createRoot(container, {hydrate: true}).render(element)?',
|
||||
'Did you mean to call root.render(element, {hydrate: true})?',
|
||||
enableStableConcurrentModeAPIs ? 'createRoot' : 'unstable_createRoot',
|
||||
);
|
||||
}
|
||||
|
||||
@@ -715,7 +715,6 @@ class ReactDOMServerRenderer {
|
||||
destroy() {
|
||||
if (!this.exhausted) {
|
||||
this.exhausted = true;
|
||||
this.clearProviders();
|
||||
freeThreadID(this.threadID);
|
||||
}
|
||||
}
|
||||
@@ -777,15 +776,6 @@ class ReactDOMServerRenderer {
|
||||
context[this.threadID] = previousValue;
|
||||
}
|
||||
|
||||
clearProviders(): void {
|
||||
// Restore any remaining providers on the stack to previous values
|
||||
for (let index = this.contextIndex; index >= 0; index--) {
|
||||
const context: ReactContext<any> = this.contextStack[index];
|
||||
const previousValue = this.contextValueStack[index];
|
||||
context[this.threadID] = previousValue;
|
||||
}
|
||||
}
|
||||
|
||||
read(bytes: number): string | null {
|
||||
if (this.exhausted) {
|
||||
return null;
|
||||
@@ -966,27 +956,9 @@ class ReactDOMServerRenderer {
|
||||
}
|
||||
case REACT_SUSPENSE_TYPE: {
|
||||
if (enableSuspenseServerRenderer) {
|
||||
const fallback = ((nextChild: any): ReactElement).props.fallback;
|
||||
if (fallback === undefined) {
|
||||
// If there is no fallback, then this just behaves as a fragment.
|
||||
const nextChildren = toArray(
|
||||
((nextChild: any): ReactElement).props.children,
|
||||
);
|
||||
const frame: Frame = {
|
||||
type: null,
|
||||
domNamespace: parentNamespace,
|
||||
children: nextChildren,
|
||||
childIndex: 0,
|
||||
context: context,
|
||||
footer: '',
|
||||
};
|
||||
if (__DEV__) {
|
||||
((frame: any): FrameDev).debugElementStack = [];
|
||||
}
|
||||
this.stack.push(frame);
|
||||
return '';
|
||||
}
|
||||
const fallbackChildren = toArray(fallback);
|
||||
const fallbackChildren = toArray(
|
||||
((nextChild: any): ReactElement).props.fallback,
|
||||
);
|
||||
const nextChildren = toArray(
|
||||
((nextChild: any): ReactElement).props.children,
|
||||
);
|
||||
@@ -1006,7 +978,7 @@ class ReactDOMServerRenderer {
|
||||
children: nextChildren,
|
||||
childIndex: 0,
|
||||
context: context,
|
||||
footer: '<!--/$-->',
|
||||
footer: '',
|
||||
};
|
||||
if (__DEV__) {
|
||||
((frame: any): FrameDev).debugElementStack = [];
|
||||
@@ -1014,7 +986,7 @@ class ReactDOMServerRenderer {
|
||||
}
|
||||
this.stack.push(frame);
|
||||
this.suspenseDepth++;
|
||||
return '<!--$-->';
|
||||
return '';
|
||||
} else {
|
||||
invariant(false, 'ReactDOMServer does not yet support Suspense.');
|
||||
}
|
||||
|
||||
22
packages/react-dom/src/shared/DOMProperty.js
vendored
22
packages/react-dom/src/shared/DOMProperty.js
vendored
@@ -525,15 +525,13 @@ const capitalize = token => token[1].toUpperCase();
|
||||
);
|
||||
});
|
||||
|
||||
// These attribute exists both in HTML and SVG.
|
||||
// The attribute name is case-sensitive in SVG so we can't just use
|
||||
// the React name like we do for attributes that exist only in HTML.
|
||||
['tabIndex', 'crossOrigin'].forEach(attributeName => {
|
||||
properties[attributeName] = new PropertyInfoRecord(
|
||||
attributeName,
|
||||
STRING,
|
||||
false, // mustUseProperty
|
||||
attributeName.toLowerCase(), // attributeName
|
||||
null, // attributeNamespace
|
||||
);
|
||||
});
|
||||
// Special case: this attribute exists both in HTML and SVG.
|
||||
// Its "tabindex" attribute name is case-sensitive in SVG so we can't just use
|
||||
// its React `tabIndex` name, like we do for attributes that exist only in HTML.
|
||||
properties.tabIndex = new PropertyInfoRecord(
|
||||
'tabIndex',
|
||||
STRING,
|
||||
false, // mustUseProperty
|
||||
'tabindex', // attributeName
|
||||
null, // attributeNamespace
|
||||
);
|
||||
|
||||
@@ -495,7 +495,7 @@ function makeSimulator(eventType) {
|
||||
|
||||
ReactDOM.unstable_batchedUpdates(function() {
|
||||
// Normally extractEvent enqueues a state restore, but we'll just always
|
||||
// do that since we're by-passing it here.
|
||||
// do that since we we're by-passing it here.
|
||||
enqueueStateRestore(domNode);
|
||||
runEventsInBatch(event);
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "react-is",
|
||||
"version": "16.8.4",
|
||||
"version": "16.8.0",
|
||||
"description": "Brand checking of React Elements.",
|
||||
"main": "index.js",
|
||||
"repository": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "react-reconciler",
|
||||
"description": "React package for creating custom renderers.",
|
||||
"version": "0.20.2",
|
||||
"version": "0.19.0",
|
||||
"keywords": [
|
||||
"react"
|
||||
],
|
||||
@@ -33,7 +33,7 @@
|
||||
"loose-envify": "^1.1.0",
|
||||
"object-assign": "^4.1.1",
|
||||
"prop-types": "^15.6.2",
|
||||
"scheduler": "^0.13.4"
|
||||
"scheduler": "^0.13.0"
|
||||
},
|
||||
"browserify": {
|
||||
"transform": [
|
||||
|
||||
@@ -736,7 +736,7 @@ function ChildReconciler(shouldTrackSideEffects) {
|
||||
newChildren: Array<*>,
|
||||
expirationTime: ExpirationTime,
|
||||
): Fiber | null {
|
||||
// This algorithm can't optimize by searching from both ends since we
|
||||
// This algorithm can't optimize by searching from boths ends since we
|
||||
// don't have backpointers on fibers. I'm trying to see how far we can get
|
||||
// with that model. If it ends up not being worth the tradeoffs, we can
|
||||
// add it later.
|
||||
|
||||
@@ -21,7 +21,6 @@ import {
|
||||
ContextConsumer,
|
||||
Mode,
|
||||
SuspenseComponent,
|
||||
DehydratedSuspenseComponent,
|
||||
} from 'shared/ReactWorkTags';
|
||||
|
||||
type MeasurementPhase =
|
||||
@@ -318,8 +317,7 @@ export function stopFailedWorkTimer(fiber: Fiber): void {
|
||||
}
|
||||
fiber._debugIsCurrentlyTiming = false;
|
||||
const warning =
|
||||
fiber.tag === SuspenseComponent ||
|
||||
fiber.tag === DehydratedSuspenseComponent
|
||||
fiber.tag === SuspenseComponent
|
||||
? 'Rendering was suspended'
|
||||
: 'An error was thrown inside this error boundary';
|
||||
endFiberMark(fiber, null, warning);
|
||||
|
||||
7
packages/react-reconciler/src/ReactFiber.js
vendored
7
packages/react-reconciler/src/ReactFiber.js
vendored
@@ -15,7 +15,6 @@ import type {SideEffectTag} from 'shared/ReactSideEffectTags';
|
||||
import type {ExpirationTime} from './ReactFiberExpirationTime';
|
||||
import type {UpdateQueue} from './ReactUpdateQueue';
|
||||
import type {ContextDependencyList} from './ReactFiberNewContext';
|
||||
import type {HookType} from './ReactFiberHooks';
|
||||
|
||||
import invariant from 'shared/invariant';
|
||||
import warningWithoutStack from 'shared/warningWithoutStack';
|
||||
@@ -205,9 +204,6 @@ export type Fiber = {|
|
||||
_debugSource?: Source | null,
|
||||
_debugOwner?: Fiber | null,
|
||||
_debugIsCurrentlyTiming?: boolean,
|
||||
|
||||
// Used to verify that the order of hooks does not change between renders.
|
||||
_debugHookTypes?: Array<HookType> | null,
|
||||
|};
|
||||
|
||||
let debugCounter;
|
||||
@@ -289,7 +285,6 @@ function FiberNode(
|
||||
this._debugSource = null;
|
||||
this._debugOwner = null;
|
||||
this._debugIsCurrentlyTiming = false;
|
||||
this._debugHookTypes = null;
|
||||
if (!hasBadMapPolyfill && typeof Object.preventExtensions === 'function') {
|
||||
Object.preventExtensions(this);
|
||||
}
|
||||
@@ -375,7 +370,6 @@ export function createWorkInProgress(
|
||||
workInProgress._debugID = current._debugID;
|
||||
workInProgress._debugSource = current._debugSource;
|
||||
workInProgress._debugOwner = current._debugOwner;
|
||||
workInProgress._debugHookTypes = current._debugHookTypes;
|
||||
}
|
||||
|
||||
workInProgress.alternate = current;
|
||||
@@ -729,6 +723,5 @@ export function assignFiberPropertiesInDEV(
|
||||
target._debugSource = source._debugSource;
|
||||
target._debugOwner = source._debugOwner;
|
||||
target._debugIsCurrentlyTiming = source._debugIsCurrentlyTiming;
|
||||
target._debugHookTypes = source._debugHookTypes;
|
||||
return target;
|
||||
}
|
||||
|
||||
130
packages/react-reconciler/src/ReactFiberBeginWork.js
vendored
130
packages/react-reconciler/src/ReactFiberBeginWork.js
vendored
@@ -30,7 +30,6 @@ import {
|
||||
ContextConsumer,
|
||||
Profiler,
|
||||
SuspenseComponent,
|
||||
DehydratedSuspenseComponent,
|
||||
MemoComponent,
|
||||
SimpleMemoComponent,
|
||||
LazyComponent,
|
||||
@@ -44,14 +43,12 @@ import {
|
||||
DidCapture,
|
||||
Update,
|
||||
Ref,
|
||||
Deletion,
|
||||
} from 'shared/ReactSideEffectTags';
|
||||
import ReactSharedInternals from 'shared/ReactSharedInternals';
|
||||
import {
|
||||
debugRenderPhaseSideEffects,
|
||||
debugRenderPhaseSideEffectsForStrictMode,
|
||||
enableProfilerTimer,
|
||||
enableSuspenseServerRenderer,
|
||||
} from 'shared/ReactFeatureFlags';
|
||||
import invariant from 'shared/invariant';
|
||||
import shallowEqual from 'shared/shallowEqual';
|
||||
@@ -106,7 +103,6 @@ import {
|
||||
} from './ReactFiberContext';
|
||||
import {
|
||||
enterHydrationState,
|
||||
reenterHydrationStateFromDehydratedSuspenseInstance,
|
||||
resetHydrationState,
|
||||
tryToClaimNextHydratableInstance,
|
||||
} from './ReactFiberHydrationContext';
|
||||
@@ -220,10 +216,6 @@ function updateForwardRef(
|
||||
nextProps: any,
|
||||
renderExpirationTime: ExpirationTime,
|
||||
) {
|
||||
// TODO: current can be non-null here even if the component
|
||||
// hasn't yet mounted. This happens after the first render suspends.
|
||||
// We'll need to figure out if this is fine or can cause issues.
|
||||
|
||||
if (__DEV__) {
|
||||
if (workInProgress.type !== workInProgress.elementType) {
|
||||
// Lazy component props can't be validated in createElement
|
||||
@@ -419,10 +411,6 @@ function updateSimpleMemoComponent(
|
||||
updateExpirationTime,
|
||||
renderExpirationTime: ExpirationTime,
|
||||
): null | Fiber {
|
||||
// TODO: current can be non-null here even if the component
|
||||
// hasn't yet mounted. This happens when the inner render suspends.
|
||||
// We'll need to figure out if this is fine or can cause issues.
|
||||
|
||||
if (__DEV__) {
|
||||
if (workInProgress.type !== workInProgress.elementType) {
|
||||
// Lazy component props can't be validated in createElement
|
||||
@@ -1404,22 +1392,6 @@ function updateSuspenseComponent(
|
||||
// children -- we skip over the primary children entirely.
|
||||
let next;
|
||||
if (current === null) {
|
||||
if (enableSuspenseServerRenderer) {
|
||||
// If we're currently hydrating, try to hydrate this boundary.
|
||||
// But only if this has a fallback.
|
||||
if (nextProps.fallback !== undefined) {
|
||||
tryToClaimNextHydratableInstance(workInProgress);
|
||||
// This could've changed the tag if this was a dehydrated suspense component.
|
||||
if (workInProgress.tag === DehydratedSuspenseComponent) {
|
||||
return updateDehydratedSuspenseComponent(
|
||||
null,
|
||||
workInProgress,
|
||||
renderExpirationTime,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This is the initial mount. This branch is pretty simple because there's
|
||||
// no previous state that needs to be preserved.
|
||||
if (nextDidTimeout) {
|
||||
@@ -1626,78 +1598,6 @@ function updateSuspenseComponent(
|
||||
return next;
|
||||
}
|
||||
|
||||
function updateDehydratedSuspenseComponent(
|
||||
current: Fiber | null,
|
||||
workInProgress: Fiber,
|
||||
renderExpirationTime: ExpirationTime,
|
||||
) {
|
||||
if (current === null) {
|
||||
// During the first pass, we'll bail out and not drill into the children.
|
||||
// Instead, we'll leave the content in place and try to hydrate it later.
|
||||
workInProgress.expirationTime = Never;
|
||||
return null;
|
||||
}
|
||||
// We use childExpirationTime to indicate that a child might depend on context, so if
|
||||
// any context has changed, we need to treat is as if the input might have changed.
|
||||
const hasContextChanged = current.childExpirationTime >= renderExpirationTime;
|
||||
if (didReceiveUpdate || hasContextChanged) {
|
||||
// This boundary has changed since the first render. This means that we are now unable to
|
||||
// hydrate it. We might still be able to hydrate it using an earlier expiration time but
|
||||
// during this render we can't. Instead, we're going to delete the whole subtree and
|
||||
// instead inject a new real Suspense boundary to take its place, which may render content
|
||||
// or fallback. The real Suspense boundary will suspend for a while so we have some time
|
||||
// to ensure it can produce real content, but all state and pending events will be lost.
|
||||
|
||||
// Detach from the current dehydrated boundary.
|
||||
current.alternate = null;
|
||||
workInProgress.alternate = null;
|
||||
|
||||
// Insert a deletion in the effect list.
|
||||
let returnFiber = workInProgress.return;
|
||||
invariant(
|
||||
returnFiber !== null,
|
||||
'Suspense boundaries are never on the root. ' +
|
||||
'This is probably a bug in React.',
|
||||
);
|
||||
const last = returnFiber.lastEffect;
|
||||
if (last !== null) {
|
||||
last.nextEffect = current;
|
||||
returnFiber.lastEffect = current;
|
||||
} else {
|
||||
returnFiber.firstEffect = returnFiber.lastEffect = current;
|
||||
}
|
||||
current.nextEffect = null;
|
||||
current.effectTag = Deletion;
|
||||
|
||||
// Upgrade this work in progress to a real Suspense component.
|
||||
workInProgress.tag = SuspenseComponent;
|
||||
workInProgress.stateNode = null;
|
||||
workInProgress.memoizedState = null;
|
||||
// This is now an insertion.
|
||||
workInProgress.effectTag |= Placement;
|
||||
// Retry as a real Suspense component.
|
||||
return updateSuspenseComponent(null, workInProgress, renderExpirationTime);
|
||||
}
|
||||
if ((workInProgress.effectTag & DidCapture) === NoEffect) {
|
||||
// This is the first attempt.
|
||||
reenterHydrationStateFromDehydratedSuspenseInstance(workInProgress);
|
||||
const nextProps = workInProgress.pendingProps;
|
||||
const nextChildren = nextProps.children;
|
||||
workInProgress.child = mountChildFibers(
|
||||
workInProgress,
|
||||
null,
|
||||
nextChildren,
|
||||
renderExpirationTime,
|
||||
);
|
||||
return workInProgress.child;
|
||||
} else {
|
||||
// Something suspended. Leave the existing children in place.
|
||||
// TODO: In non-concurrent mode, should we commit the nodes we have hydrated so far?
|
||||
workInProgress.child = null;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function updatePortalComponent(
|
||||
current: Fiber | null,
|
||||
workInProgress: Fiber,
|
||||
@@ -1982,15 +1882,6 @@ function beginWork(
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DehydratedSuspenseComponent: {
|
||||
if (enableSuspenseServerRenderer) {
|
||||
// We know that this component will suspend again because if it has
|
||||
// been unsuspended it has committed as a regular Suspense component.
|
||||
// If it needs to be retried, it should have work scheduled on it.
|
||||
workInProgress.effectTag |= DidCapture;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return bailoutOnAlreadyFinishedWork(
|
||||
current,
|
||||
@@ -2160,22 +2051,13 @@ function beginWork(
|
||||
renderExpirationTime,
|
||||
);
|
||||
}
|
||||
case DehydratedSuspenseComponent: {
|
||||
if (enableSuspenseServerRenderer) {
|
||||
return updateDehydratedSuspenseComponent(
|
||||
current,
|
||||
workInProgress,
|
||||
renderExpirationTime,
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
invariant(
|
||||
false,
|
||||
'Unknown unit of work tag. This error is likely caused by a bug in ' +
|
||||
'React. Please file an issue.',
|
||||
);
|
||||
}
|
||||
invariant(
|
||||
false,
|
||||
'Unknown unit of work tag. This error is likely caused by a bug in ' +
|
||||
'React. Please file an issue.',
|
||||
);
|
||||
}
|
||||
|
||||
export {beginWork};
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
import type {
|
||||
Instance,
|
||||
TextInstance,
|
||||
SuspenseInstance,
|
||||
Container,
|
||||
ChildSet,
|
||||
UpdatePayload,
|
||||
@@ -27,7 +26,6 @@ import {unstable_wrap as Schedule_tracing_wrap} from 'scheduler/tracing';
|
||||
import {
|
||||
enableSchedulerTracing,
|
||||
enableProfilerTimer,
|
||||
enableSuspenseServerRenderer,
|
||||
} from 'shared/ReactFeatureFlags';
|
||||
import {
|
||||
FunctionComponent,
|
||||
@@ -39,7 +37,6 @@ import {
|
||||
HostPortal,
|
||||
Profiler,
|
||||
SuspenseComponent,
|
||||
DehydratedSuspenseComponent,
|
||||
IncompleteClassComponent,
|
||||
MemoComponent,
|
||||
SimpleMemoComponent,
|
||||
@@ -82,8 +79,6 @@ import {
|
||||
insertInContainerBefore,
|
||||
removeChild,
|
||||
removeChildFromContainer,
|
||||
clearSuspenseBoundary,
|
||||
clearSuspenseBoundaryFromContainer,
|
||||
replaceContainerChildren,
|
||||
createContainerChildSet,
|
||||
hideInstance,
|
||||
@@ -598,7 +593,7 @@ function commitLifeCycles(
|
||||
|
||||
function hideOrUnhideAllChildren(finishedWork, isHidden) {
|
||||
if (supportsMutation) {
|
||||
// We only have the top Fiber that was inserted but we need to recurse down its
|
||||
// We only have the top Fiber that was inserted but we need recurse down its
|
||||
// children to find all the terminal nodes.
|
||||
let node: Fiber = finishedWork;
|
||||
while (true) {
|
||||
@@ -886,11 +881,7 @@ function getHostSibling(fiber: Fiber): ?Instance {
|
||||
}
|
||||
node.sibling.return = node.return;
|
||||
node = node.sibling;
|
||||
while (
|
||||
node.tag !== HostComponent &&
|
||||
node.tag !== HostText &&
|
||||
node.tag !== DehydratedSuspenseComponent
|
||||
) {
|
||||
while (node.tag !== HostComponent && node.tag !== HostText) {
|
||||
// If it is not host node and, we might have a host node inside it.
|
||||
// Try to search down until we find one.
|
||||
if (node.effectTag & Placement) {
|
||||
@@ -954,7 +945,7 @@ function commitPlacement(finishedWork: Fiber): void {
|
||||
}
|
||||
|
||||
const before = getHostSibling(finishedWork);
|
||||
// We only have the top Fiber that was inserted but we need to recurse down its
|
||||
// We only have the top Fiber that was inserted but we need recurse down its
|
||||
// children to find all the terminal nodes.
|
||||
let node: Fiber = finishedWork;
|
||||
while (true) {
|
||||
@@ -996,7 +987,7 @@ function commitPlacement(finishedWork: Fiber): void {
|
||||
}
|
||||
|
||||
function unmountHostComponents(current): void {
|
||||
// We only have the top Fiber that was deleted but we need to recurse down its
|
||||
// We only have the top Fiber that was deleted but we need recurse down its
|
||||
// children to find all the terminal nodes.
|
||||
let node: Fiber = current;
|
||||
|
||||
@@ -1041,40 +1032,18 @@ function unmountHostComponents(current): void {
|
||||
// After all the children have unmounted, it is now safe to remove the
|
||||
// node from the tree.
|
||||
if (currentParentIsContainer) {
|
||||
removeChildFromContainer(
|
||||
((currentParent: any): Container),
|
||||
(node.stateNode: Instance | TextInstance),
|
||||
);
|
||||
removeChildFromContainer((currentParent: any), node.stateNode);
|
||||
} else {
|
||||
removeChild(
|
||||
((currentParent: any): Instance),
|
||||
(node.stateNode: Instance | TextInstance),
|
||||
);
|
||||
removeChild((currentParent: any), node.stateNode);
|
||||
}
|
||||
// Don't visit children because we already visited them.
|
||||
} else if (
|
||||
enableSuspenseServerRenderer &&
|
||||
node.tag === DehydratedSuspenseComponent
|
||||
) {
|
||||
// Delete the dehydrated suspense boundary and all of its content.
|
||||
if (currentParentIsContainer) {
|
||||
clearSuspenseBoundaryFromContainer(
|
||||
((currentParent: any): Container),
|
||||
(node.stateNode: SuspenseInstance),
|
||||
);
|
||||
} else {
|
||||
clearSuspenseBoundary(
|
||||
((currentParent: any): Instance),
|
||||
(node.stateNode: SuspenseInstance),
|
||||
);
|
||||
}
|
||||
} else if (node.tag === HostPortal) {
|
||||
// When we go into a portal, it becomes the parent to remove from.
|
||||
// We will reassign it back when we pop the portal on the way up.
|
||||
currentParent = node.stateNode.containerInfo;
|
||||
currentParentIsContainer = true;
|
||||
// Visit children because portals might contain host components.
|
||||
if (node.child !== null) {
|
||||
// When we go into a portal, it becomes the parent to remove from.
|
||||
// We will reassign it back when we pop the portal on the way up.
|
||||
currentParent = node.stateNode.containerInfo;
|
||||
currentParentIsContainer = true;
|
||||
// Visit children because portals might contain host components.
|
||||
node.child.return = node;
|
||||
node = node.child;
|
||||
continue;
|
||||
|
||||
@@ -34,7 +34,6 @@ import {
|
||||
Mode,
|
||||
Profiler,
|
||||
SuspenseComponent,
|
||||
DehydratedSuspenseComponent,
|
||||
MemoComponent,
|
||||
SimpleMemoComponent,
|
||||
LazyComponent,
|
||||
@@ -81,10 +80,8 @@ import {popProvider} from './ReactFiberNewContext';
|
||||
import {
|
||||
prepareToHydrateHostInstance,
|
||||
prepareToHydrateHostTextInstance,
|
||||
skipPastDehydratedSuspenseInstance,
|
||||
popHydrationState,
|
||||
} from './ReactFiberHydrationContext';
|
||||
import {enableSuspenseServerRenderer} from 'shared/ReactFeatureFlags';
|
||||
|
||||
function markUpdate(workInProgress: Fiber) {
|
||||
// Tag the fiber with an update effect. This turns a Placement into
|
||||
@@ -765,29 +762,6 @@ function completeWork(
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DehydratedSuspenseComponent: {
|
||||
if (enableSuspenseServerRenderer) {
|
||||
if (current === null) {
|
||||
let wasHydrated = popHydrationState(workInProgress);
|
||||
invariant(
|
||||
wasHydrated,
|
||||
'A dehydrated suspense component was completed without a hydrated node. ' +
|
||||
'This is probably a bug in React.',
|
||||
);
|
||||
skipPastDehydratedSuspenseInstance(workInProgress);
|
||||
} else if ((workInProgress.effectTag & DidCapture) === NoEffect) {
|
||||
// This boundary did not suspend so it's now hydrated.
|
||||
// To handle any future suspense cases, we're going to now upgrade it
|
||||
// to a Suspense component. We detach it from the existing current fiber.
|
||||
current.alternate = null;
|
||||
workInProgress.alternate = null;
|
||||
workInProgress.tag = SuspenseComponent;
|
||||
workInProgress.memoizedState = null;
|
||||
workInProgress.stateNode = null;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
invariant(
|
||||
false,
|
||||
|
||||
372
packages/react-reconciler/src/ReactFiberHooks.js
vendored
372
packages/react-reconciler/src/ReactFiberHooks.js
vendored
@@ -93,7 +93,7 @@ type UpdateQueue<S, A> = {
|
||||
eagerState: S | null,
|
||||
};
|
||||
|
||||
export type HookType =
|
||||
type HookType =
|
||||
| 'useState'
|
||||
| 'useReducer'
|
||||
| 'useContext'
|
||||
@@ -120,6 +120,10 @@ export type Hook = {
|
||||
next: Hook | null,
|
||||
};
|
||||
|
||||
type HookDev = Hook & {
|
||||
_debugType: HookType,
|
||||
};
|
||||
|
||||
type Effect = {
|
||||
tag: HookEffectTag,
|
||||
create: () => (() => void) | void,
|
||||
@@ -146,6 +150,7 @@ let currentlyRenderingFiber: Fiber | null = null;
|
||||
// current hook list is the list that belongs to the current fiber. The
|
||||
// work-in-progress hook list is a new list that will be added to the
|
||||
// work-in-progress fiber.
|
||||
let firstCurrentHook: Hook | null = null;
|
||||
let currentHook: Hook | null = null;
|
||||
let nextCurrentHook: Hook | null = null;
|
||||
let firstWorkInProgressHook: Hook | null = null;
|
||||
@@ -178,38 +183,7 @@ const RE_RENDER_LIMIT = 25;
|
||||
// In DEV, this is the name of the currently executing primitive hook
|
||||
let currentHookNameInDev: ?HookType = null;
|
||||
|
||||
// In DEV, this list ensures that hooks are called in the same order between renders.
|
||||
// The list stores the order of hooks used during the initial render (mount).
|
||||
// Subsequent renders (updates) reference this list.
|
||||
let hookTypesDev: Array<HookType> | null = null;
|
||||
let hookTypesUpdateIndexDev: number = -1;
|
||||
|
||||
function mountHookTypesDev() {
|
||||
if (__DEV__) {
|
||||
const hookName = ((currentHookNameInDev: any): HookType);
|
||||
|
||||
if (hookTypesDev === null) {
|
||||
hookTypesDev = [hookName];
|
||||
} else {
|
||||
hookTypesDev.push(hookName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function updateHookTypesDev() {
|
||||
if (__DEV__) {
|
||||
const hookName = ((currentHookNameInDev: any): HookType);
|
||||
|
||||
if (hookTypesDev !== null) {
|
||||
hookTypesUpdateIndexDev++;
|
||||
if (hookTypesDev[hookTypesUpdateIndexDev] !== hookName) {
|
||||
warnOnHookMismatchInDev(hookName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function warnOnHookMismatchInDev(currentHookName: HookType) {
|
||||
function warnOnHookMismatchInDev() {
|
||||
if (__DEV__) {
|
||||
const componentName = getComponentName(
|
||||
((currentlyRenderingFiber: any): Fiber).type,
|
||||
@@ -217,44 +191,44 @@ function warnOnHookMismatchInDev(currentHookName: HookType) {
|
||||
if (!didWarnAboutMismatchedHooksForComponent.has(componentName)) {
|
||||
didWarnAboutMismatchedHooksForComponent.add(componentName);
|
||||
|
||||
if (hookTypesDev !== null) {
|
||||
let table = '';
|
||||
const secondColumnStart = 22;
|
||||
|
||||
const secondColumnStart = 30;
|
||||
let table = '';
|
||||
let prevHook: HookDev | null = (firstCurrentHook: any);
|
||||
let nextHook: HookDev | null = (firstWorkInProgressHook: any);
|
||||
let n = 1;
|
||||
while (prevHook !== null && nextHook !== null) {
|
||||
const oldHookName = prevHook._debugType;
|
||||
const newHookName = nextHook._debugType;
|
||||
|
||||
for (let i = 0; i <= ((hookTypesUpdateIndexDev: any): number); i++) {
|
||||
const oldHookName = hookTypesDev[i];
|
||||
const newHookName =
|
||||
i === ((hookTypesUpdateIndexDev: any): number)
|
||||
? currentHookName
|
||||
: oldHookName;
|
||||
let row = `${n}. ${oldHookName}`;
|
||||
|
||||
let row = `${i + 1}. ${oldHookName}`;
|
||||
|
||||
// Extra space so second column lines up
|
||||
// lol @ IE not supporting String#repeat
|
||||
while (row.length < secondColumnStart) {
|
||||
row += ' ';
|
||||
}
|
||||
|
||||
row += newHookName + '\n';
|
||||
|
||||
table += row;
|
||||
// Extra space so second column lines up
|
||||
// lol @ IE not supporting String#repeat
|
||||
while (row.length < secondColumnStart) {
|
||||
row += ' ';
|
||||
}
|
||||
|
||||
warning(
|
||||
false,
|
||||
'React has detected a change in the order of Hooks called by %s. ' +
|
||||
'This will lead to bugs and errors if not fixed. ' +
|
||||
'For more information, read the Rules of Hooks: https://fb.me/rules-of-hooks\n\n' +
|
||||
' Previous render Next render\n' +
|
||||
' ------------------------------------------------------\n' +
|
||||
'%s' +
|
||||
' ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n',
|
||||
componentName,
|
||||
table,
|
||||
);
|
||||
row += newHookName + '\n';
|
||||
|
||||
table += row;
|
||||
prevHook = (prevHook.next: any);
|
||||
nextHook = (nextHook.next: any);
|
||||
n++;
|
||||
}
|
||||
|
||||
warning(
|
||||
false,
|
||||
'React has detected a change in the order of Hooks called by %s. ' +
|
||||
'This will lead to bugs and errors if not fixed. ' +
|
||||
'For more information, read the Rules of Hooks: https://fb.me/rules-of-hooks\n\n' +
|
||||
' Previous render Next render\n' +
|
||||
' -------------------------------\n' +
|
||||
'%s' +
|
||||
' ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n',
|
||||
componentName,
|
||||
table,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -319,15 +293,8 @@ export function renderWithHooks(
|
||||
): any {
|
||||
renderExpirationTime = nextRenderExpirationTime;
|
||||
currentlyRenderingFiber = workInProgress;
|
||||
nextCurrentHook = current !== null ? current.memoizedState : null;
|
||||
|
||||
if (__DEV__) {
|
||||
hookTypesDev =
|
||||
current !== null
|
||||
? ((current._debugHookTypes: any): Array<HookType>)
|
||||
: null;
|
||||
hookTypesUpdateIndexDev = -1;
|
||||
}
|
||||
firstCurrentHook = nextCurrentHook =
|
||||
current !== null ? current.memoizedState : null;
|
||||
|
||||
// The following should have already been reset
|
||||
// currentHook = null;
|
||||
@@ -341,26 +308,11 @@ export function renderWithHooks(
|
||||
// numberOfReRenders = 0;
|
||||
// sideEffectTag = 0;
|
||||
|
||||
// TODO Warn if no hooks are used at all during mount, then some are used during update.
|
||||
// Currently we will identify the update render as a mount because nextCurrentHook === null.
|
||||
// This is tricky because it's valid for certain types of components (e.g. React.lazy)
|
||||
|
||||
// Using nextCurrentHook to differentiate between mount/update only works if at least one stateful hook is used.
|
||||
// Non-stateful hooks (e.g. context) don't get added to memoizedState,
|
||||
// so nextCurrentHook would be null during updates and mounts.
|
||||
if (__DEV__) {
|
||||
if (nextCurrentHook !== null) {
|
||||
ReactCurrentDispatcher.current = HooksDispatcherOnUpdateInDEV;
|
||||
} else if (hookTypesDev !== null) {
|
||||
// This dispatcher handles an edge case where a component is updating,
|
||||
// but no stateful hooks have been used.
|
||||
// We want to match the production code behavior (which will use HooksDispatcherOnMount),
|
||||
// but with the extra DEV validation to ensure hooks ordering hasn't changed.
|
||||
// This dispatcher does that.
|
||||
ReactCurrentDispatcher.current = HooksDispatcherOnMountWithHookTypesInDEV;
|
||||
} else {
|
||||
ReactCurrentDispatcher.current = HooksDispatcherOnMountInDEV;
|
||||
}
|
||||
ReactCurrentDispatcher.current =
|
||||
nextCurrentHook === null
|
||||
? HooksDispatcherOnMountInDEV
|
||||
: HooksDispatcherOnUpdateInDEV;
|
||||
} else {
|
||||
ReactCurrentDispatcher.current =
|
||||
nextCurrentHook === null
|
||||
@@ -376,18 +328,14 @@ export function renderWithHooks(
|
||||
numberOfReRenders += 1;
|
||||
|
||||
// Start over from the beginning of the list
|
||||
nextCurrentHook = current !== null ? current.memoizedState : null;
|
||||
firstCurrentHook = nextCurrentHook =
|
||||
current !== null ? current.memoizedState : null;
|
||||
nextWorkInProgressHook = firstWorkInProgressHook;
|
||||
|
||||
currentHook = null;
|
||||
workInProgressHook = null;
|
||||
componentUpdateQueue = null;
|
||||
|
||||
if (__DEV__) {
|
||||
// Also validate hook order for cascading updates.
|
||||
hookTypesUpdateIndexDev = -1;
|
||||
}
|
||||
|
||||
ReactCurrentDispatcher.current = __DEV__
|
||||
? HooksDispatcherOnUpdateInDEV
|
||||
: HooksDispatcherOnUpdate;
|
||||
@@ -399,6 +347,10 @@ export function renderWithHooks(
|
||||
numberOfReRenders = 0;
|
||||
}
|
||||
|
||||
if (__DEV__) {
|
||||
currentHookNameInDev = null;
|
||||
}
|
||||
|
||||
// We can assume the previous dispatcher is always this one, since we set it
|
||||
// at the beginning of the render phase and there's no re-entrancy.
|
||||
ReactCurrentDispatcher.current = ContextOnlyDispatcher;
|
||||
@@ -410,30 +362,19 @@ export function renderWithHooks(
|
||||
renderedWork.updateQueue = (componentUpdateQueue: any);
|
||||
renderedWork.effectTag |= sideEffectTag;
|
||||
|
||||
if (__DEV__) {
|
||||
renderedWork._debugHookTypes = hookTypesDev;
|
||||
}
|
||||
|
||||
// This check uses currentHook so that it works the same in DEV and prod bundles.
|
||||
// hookTypesDev could catch more cases (e.g. context) but only in DEV bundles.
|
||||
const didRenderTooFewHooks =
|
||||
currentHook !== null && currentHook.next !== null;
|
||||
|
||||
renderExpirationTime = NoWork;
|
||||
currentlyRenderingFiber = null;
|
||||
|
||||
firstCurrentHook = null;
|
||||
currentHook = null;
|
||||
nextCurrentHook = null;
|
||||
firstWorkInProgressHook = null;
|
||||
workInProgressHook = null;
|
||||
nextWorkInProgressHook = null;
|
||||
|
||||
if (__DEV__) {
|
||||
currentHookNameInDev = null;
|
||||
hookTypesDev = null;
|
||||
hookTypesUpdateIndexDev = -1;
|
||||
}
|
||||
|
||||
remainingExpirationTime = NoWork;
|
||||
componentUpdateQueue = null;
|
||||
sideEffectTag = 0;
|
||||
@@ -475,23 +416,21 @@ export function resetHooks(): void {
|
||||
renderExpirationTime = NoWork;
|
||||
currentlyRenderingFiber = null;
|
||||
|
||||
firstCurrentHook = null;
|
||||
currentHook = null;
|
||||
nextCurrentHook = null;
|
||||
firstWorkInProgressHook = null;
|
||||
workInProgressHook = null;
|
||||
nextWorkInProgressHook = null;
|
||||
|
||||
if (__DEV__) {
|
||||
hookTypesDev = null;
|
||||
hookTypesUpdateIndexDev = -1;
|
||||
|
||||
currentHookNameInDev = null;
|
||||
}
|
||||
|
||||
remainingExpirationTime = NoWork;
|
||||
componentUpdateQueue = null;
|
||||
sideEffectTag = 0;
|
||||
|
||||
if (__DEV__) {
|
||||
currentHookNameInDev = null;
|
||||
}
|
||||
|
||||
didScheduleRenderPhaseUpdate = false;
|
||||
renderPhaseUpdates = null;
|
||||
numberOfReRenders = 0;
|
||||
@@ -508,6 +447,20 @@ function mountWorkInProgressHook(): Hook {
|
||||
next: null,
|
||||
};
|
||||
|
||||
if (__DEV__) {
|
||||
(hook: any)._debugType = (currentHookNameInDev: any);
|
||||
if (
|
||||
currentlyRenderingFiber !== null &&
|
||||
currentlyRenderingFiber.alternate !== null
|
||||
) {
|
||||
warning(
|
||||
false,
|
||||
'%s: Rendered more hooks than during the previous render. This is ' +
|
||||
'not currently supported and may lead to unexpected behavior.',
|
||||
getComponentName(currentlyRenderingFiber.type),
|
||||
);
|
||||
}
|
||||
}
|
||||
if (workInProgressHook === null) {
|
||||
// This is the first hook in the list
|
||||
firstWorkInProgressHook = workInProgressHook = hook;
|
||||
@@ -557,6 +510,13 @@ function updateWorkInProgressHook(): Hook {
|
||||
workInProgressHook = workInProgressHook.next = newHook;
|
||||
}
|
||||
nextCurrentHook = currentHook.next;
|
||||
|
||||
if (__DEV__) {
|
||||
(newHook: any)._debugType = (currentHookNameInDev: any);
|
||||
if (currentHookNameInDev !== ((currentHook: any): HookDev)._debugType) {
|
||||
warnOnHookMismatchInDev();
|
||||
}
|
||||
}
|
||||
}
|
||||
return workInProgressHook;
|
||||
}
|
||||
@@ -571,6 +531,26 @@ function basicStateReducer<S>(state: S, action: BasicStateAction<S>): S {
|
||||
return typeof action === 'function' ? action(state) : action;
|
||||
}
|
||||
|
||||
function mountContext<T>(
|
||||
context: ReactContext<T>,
|
||||
observedBits: void | number | boolean,
|
||||
): T {
|
||||
if (__DEV__) {
|
||||
mountWorkInProgressHook();
|
||||
}
|
||||
return readContext(context, observedBits);
|
||||
}
|
||||
|
||||
function updateContext<T>(
|
||||
context: ReactContext<T>,
|
||||
observedBits: void | number | boolean,
|
||||
): T {
|
||||
if (__DEV__) {
|
||||
updateWorkInProgressHook();
|
||||
}
|
||||
return readContext(context, observedBits);
|
||||
}
|
||||
|
||||
function mountReducer<S, I, A>(
|
||||
reducer: (S, A) => S,
|
||||
initialArg: I,
|
||||
@@ -638,6 +618,7 @@ function updateReducer<S, I, A>(
|
||||
}
|
||||
|
||||
hook.memoizedState = newState;
|
||||
|
||||
// Don't persist the state accumlated from the render phase updates to
|
||||
// the base state unless the queue is empty.
|
||||
// TODO: Not sure if this is the desired semantics, but it's what we
|
||||
@@ -646,9 +627,6 @@ function updateReducer<S, I, A>(
|
||||
hook.baseState = newState;
|
||||
}
|
||||
|
||||
queue.eagerReducer = reducer;
|
||||
queue.eagerState = newState;
|
||||
|
||||
return [newState, dispatch];
|
||||
}
|
||||
}
|
||||
@@ -927,7 +905,7 @@ function mountImperativeHandle<T>(
|
||||
|
||||
// TODO: If deps are provided, should we skip comparing the ref itself?
|
||||
const effectDeps =
|
||||
deps !== null && deps !== undefined ? deps.concat([ref]) : null;
|
||||
deps !== null && deps !== undefined ? deps.concat([ref]) : [ref];
|
||||
|
||||
return mountEffectImpl(
|
||||
UpdateEffect,
|
||||
@@ -953,7 +931,7 @@ function updateImperativeHandle<T>(
|
||||
|
||||
// TODO: If deps are provided, should we skip comparing the ref itself?
|
||||
const effectDeps =
|
||||
deps !== null && deps !== undefined ? deps.concat([ref]) : null;
|
||||
deps !== null && deps !== undefined ? deps.concat([ref]) : [ref];
|
||||
|
||||
return updateEffectImpl(
|
||||
UpdateEffect,
|
||||
@@ -1031,7 +1009,7 @@ function updateMemo<T>(
|
||||
let shouldWarnForUnbatchedSetState = false;
|
||||
|
||||
if (__DEV__) {
|
||||
// jest isn't a 'global', it's just exposed to tests via a wrapped function
|
||||
// jest isnt' a 'global', it's just exposed to tests via a wrapped function
|
||||
// further, this isn't a test file, so flow doesn't recognize the symbol. So...
|
||||
// $FlowExpectedError - because requirements don't give a damn about your type sigs.
|
||||
if ('undefined' !== typeof jest) {
|
||||
@@ -1212,7 +1190,6 @@ const HooksDispatcherOnUpdate: Dispatcher = {
|
||||
};
|
||||
|
||||
let HooksDispatcherOnMountInDEV: Dispatcher | null = null;
|
||||
let HooksDispatcherOnMountWithHookTypesInDEV: Dispatcher | null = null;
|
||||
let HooksDispatcherOnUpdateInDEV: Dispatcher | null = null;
|
||||
let InvalidNestedHooksDispatcherOnMountInDEV: Dispatcher | null = null;
|
||||
let InvalidNestedHooksDispatcherOnUpdateInDEV: Dispatcher | null = null;
|
||||
@@ -1248,7 +1225,6 @@ if (__DEV__) {
|
||||
|
||||
useCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
|
||||
currentHookNameInDev = 'useCallback';
|
||||
mountHookTypesDev();
|
||||
return mountCallback(callback, deps);
|
||||
},
|
||||
useContext<T>(
|
||||
@@ -1256,15 +1232,13 @@ if (__DEV__) {
|
||||
observedBits: void | number | boolean,
|
||||
): T {
|
||||
currentHookNameInDev = 'useContext';
|
||||
mountHookTypesDev();
|
||||
return readContext(context, observedBits);
|
||||
return mountContext(context, observedBits);
|
||||
},
|
||||
useEffect(
|
||||
create: () => (() => void) | void,
|
||||
deps: Array<mixed> | void | null,
|
||||
): void {
|
||||
currentHookNameInDev = 'useEffect';
|
||||
mountHookTypesDev();
|
||||
return mountEffect(create, deps);
|
||||
},
|
||||
useImperativeHandle<T>(
|
||||
@@ -1273,7 +1247,6 @@ if (__DEV__) {
|
||||
deps: Array<mixed> | void | null,
|
||||
): void {
|
||||
currentHookNameInDev = 'useImperativeHandle';
|
||||
mountHookTypesDev();
|
||||
return mountImperativeHandle(ref, create, deps);
|
||||
},
|
||||
useLayoutEffect(
|
||||
@@ -1281,12 +1254,10 @@ if (__DEV__) {
|
||||
deps: Array<mixed> | void | null,
|
||||
): void {
|
||||
currentHookNameInDev = 'useLayoutEffect';
|
||||
mountHookTypesDev();
|
||||
return mountLayoutEffect(create, deps);
|
||||
},
|
||||
useMemo<T>(create: () => T, deps: Array<mixed> | void | null): T {
|
||||
currentHookNameInDev = 'useMemo';
|
||||
mountHookTypesDev();
|
||||
const prevDispatcher = ReactCurrentDispatcher.current;
|
||||
ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnMountInDEV;
|
||||
try {
|
||||
@@ -1301,7 +1272,6 @@ if (__DEV__) {
|
||||
init?: I => S,
|
||||
): [S, Dispatch<A>] {
|
||||
currentHookNameInDev = 'useReducer';
|
||||
mountHookTypesDev();
|
||||
const prevDispatcher = ReactCurrentDispatcher.current;
|
||||
ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnMountInDEV;
|
||||
try {
|
||||
@@ -1312,14 +1282,12 @@ if (__DEV__) {
|
||||
},
|
||||
useRef<T>(initialValue: T): {current: T} {
|
||||
currentHookNameInDev = 'useRef';
|
||||
mountHookTypesDev();
|
||||
return mountRef(initialValue);
|
||||
},
|
||||
useState<S>(
|
||||
initialState: (() => S) | S,
|
||||
): [S, Dispatch<BasicStateAction<S>>] {
|
||||
currentHookNameInDev = 'useState';
|
||||
mountHookTypesDev();
|
||||
const prevDispatcher = ReactCurrentDispatcher.current;
|
||||
ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnMountInDEV;
|
||||
try {
|
||||
@@ -1330,104 +1298,6 @@ if (__DEV__) {
|
||||
},
|
||||
useDebugValue<T>(value: T, formatterFn: ?(value: T) => mixed): void {
|
||||
currentHookNameInDev = 'useDebugValue';
|
||||
mountHookTypesDev();
|
||||
return mountDebugValue(value, formatterFn);
|
||||
},
|
||||
};
|
||||
|
||||
HooksDispatcherOnMountWithHookTypesInDEV = {
|
||||
readContext<T>(
|
||||
context: ReactContext<T>,
|
||||
observedBits: void | number | boolean,
|
||||
): T {
|
||||
return readContext(context, observedBits);
|
||||
},
|
||||
|
||||
useCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
|
||||
currentHookNameInDev = 'useCallback';
|
||||
updateHookTypesDev();
|
||||
return mountCallback(callback, deps);
|
||||
},
|
||||
useContext<T>(
|
||||
context: ReactContext<T>,
|
||||
observedBits: void | number | boolean,
|
||||
): T {
|
||||
currentHookNameInDev = 'useContext';
|
||||
updateHookTypesDev();
|
||||
return readContext(context, observedBits);
|
||||
},
|
||||
useEffect(
|
||||
create: () => (() => void) | void,
|
||||
deps: Array<mixed> | void | null,
|
||||
): void {
|
||||
currentHookNameInDev = 'useEffect';
|
||||
updateHookTypesDev();
|
||||
return mountEffect(create, deps);
|
||||
},
|
||||
useImperativeHandle<T>(
|
||||
ref: {current: T | null} | ((inst: T | null) => mixed) | null | void,
|
||||
create: () => T,
|
||||
deps: Array<mixed> | void | null,
|
||||
): void {
|
||||
currentHookNameInDev = 'useImperativeHandle';
|
||||
updateHookTypesDev();
|
||||
return mountImperativeHandle(ref, create, deps);
|
||||
},
|
||||
useLayoutEffect(
|
||||
create: () => (() => void) | void,
|
||||
deps: Array<mixed> | void | null,
|
||||
): void {
|
||||
currentHookNameInDev = 'useLayoutEffect';
|
||||
updateHookTypesDev();
|
||||
return mountLayoutEffect(create, deps);
|
||||
},
|
||||
useMemo<T>(create: () => T, deps: Array<mixed> | void | null): T {
|
||||
currentHookNameInDev = 'useMemo';
|
||||
updateHookTypesDev();
|
||||
const prevDispatcher = ReactCurrentDispatcher.current;
|
||||
ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnMountInDEV;
|
||||
try {
|
||||
return mountMemo(create, deps);
|
||||
} finally {
|
||||
ReactCurrentDispatcher.current = prevDispatcher;
|
||||
}
|
||||
},
|
||||
useReducer<S, I, A>(
|
||||
reducer: (S, A) => S,
|
||||
initialArg: I,
|
||||
init?: I => S,
|
||||
): [S, Dispatch<A>] {
|
||||
currentHookNameInDev = 'useReducer';
|
||||
updateHookTypesDev();
|
||||
const prevDispatcher = ReactCurrentDispatcher.current;
|
||||
ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnMountInDEV;
|
||||
try {
|
||||
return mountReducer(reducer, initialArg, init);
|
||||
} finally {
|
||||
ReactCurrentDispatcher.current = prevDispatcher;
|
||||
}
|
||||
},
|
||||
useRef<T>(initialValue: T): {current: T} {
|
||||
currentHookNameInDev = 'useRef';
|
||||
updateHookTypesDev();
|
||||
return mountRef(initialValue);
|
||||
},
|
||||
useState<S>(
|
||||
initialState: (() => S) | S,
|
||||
): [S, Dispatch<BasicStateAction<S>>] {
|
||||
currentHookNameInDev = 'useState';
|
||||
updateHookTypesDev();
|
||||
const prevDispatcher = ReactCurrentDispatcher.current;
|
||||
ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnMountInDEV;
|
||||
try {
|
||||
return mountState(initialState);
|
||||
} finally {
|
||||
ReactCurrentDispatcher.current = prevDispatcher;
|
||||
}
|
||||
},
|
||||
useDebugValue<T>(value: T, formatterFn: ?(value: T) => mixed): void {
|
||||
currentHookNameInDev = 'useDebugValue';
|
||||
updateHookTypesDev();
|
||||
return mountDebugValue(value, formatterFn);
|
||||
},
|
||||
};
|
||||
@@ -1442,7 +1312,6 @@ if (__DEV__) {
|
||||
|
||||
useCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
|
||||
currentHookNameInDev = 'useCallback';
|
||||
updateHookTypesDev();
|
||||
return updateCallback(callback, deps);
|
||||
},
|
||||
useContext<T>(
|
||||
@@ -1450,15 +1319,13 @@ if (__DEV__) {
|
||||
observedBits: void | number | boolean,
|
||||
): T {
|
||||
currentHookNameInDev = 'useContext';
|
||||
updateHookTypesDev();
|
||||
return readContext(context, observedBits);
|
||||
return updateContext(context, observedBits);
|
||||
},
|
||||
useEffect(
|
||||
create: () => (() => void) | void,
|
||||
deps: Array<mixed> | void | null,
|
||||
): void {
|
||||
currentHookNameInDev = 'useEffect';
|
||||
updateHookTypesDev();
|
||||
return updateEffect(create, deps);
|
||||
},
|
||||
useImperativeHandle<T>(
|
||||
@@ -1467,7 +1334,6 @@ if (__DEV__) {
|
||||
deps: Array<mixed> | void | null,
|
||||
): void {
|
||||
currentHookNameInDev = 'useImperativeHandle';
|
||||
updateHookTypesDev();
|
||||
return updateImperativeHandle(ref, create, deps);
|
||||
},
|
||||
useLayoutEffect(
|
||||
@@ -1475,12 +1341,10 @@ if (__DEV__) {
|
||||
deps: Array<mixed> | void | null,
|
||||
): void {
|
||||
currentHookNameInDev = 'useLayoutEffect';
|
||||
updateHookTypesDev();
|
||||
return updateLayoutEffect(create, deps);
|
||||
},
|
||||
useMemo<T>(create: () => T, deps: Array<mixed> | void | null): T {
|
||||
currentHookNameInDev = 'useMemo';
|
||||
updateHookTypesDev();
|
||||
const prevDispatcher = ReactCurrentDispatcher.current;
|
||||
ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnUpdateInDEV;
|
||||
try {
|
||||
@@ -1495,7 +1359,6 @@ if (__DEV__) {
|
||||
init?: I => S,
|
||||
): [S, Dispatch<A>] {
|
||||
currentHookNameInDev = 'useReducer';
|
||||
updateHookTypesDev();
|
||||
const prevDispatcher = ReactCurrentDispatcher.current;
|
||||
ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnUpdateInDEV;
|
||||
try {
|
||||
@@ -1506,14 +1369,12 @@ if (__DEV__) {
|
||||
},
|
||||
useRef<T>(initialValue: T): {current: T} {
|
||||
currentHookNameInDev = 'useRef';
|
||||
updateHookTypesDev();
|
||||
return updateRef(initialValue);
|
||||
},
|
||||
useState<S>(
|
||||
initialState: (() => S) | S,
|
||||
): [S, Dispatch<BasicStateAction<S>>] {
|
||||
currentHookNameInDev = 'useState';
|
||||
updateHookTypesDev();
|
||||
const prevDispatcher = ReactCurrentDispatcher.current;
|
||||
ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnUpdateInDEV;
|
||||
try {
|
||||
@@ -1524,7 +1385,6 @@ if (__DEV__) {
|
||||
},
|
||||
useDebugValue<T>(value: T, formatterFn: ?(value: T) => mixed): void {
|
||||
currentHookNameInDev = 'useDebugValue';
|
||||
updateHookTypesDev();
|
||||
return updateDebugValue(value, formatterFn);
|
||||
},
|
||||
};
|
||||
@@ -1541,7 +1401,6 @@ if (__DEV__) {
|
||||
useCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
|
||||
currentHookNameInDev = 'useCallback';
|
||||
warnInvalidHookAccess();
|
||||
mountHookTypesDev();
|
||||
return mountCallback(callback, deps);
|
||||
},
|
||||
useContext<T>(
|
||||
@@ -1550,8 +1409,7 @@ if (__DEV__) {
|
||||
): T {
|
||||
currentHookNameInDev = 'useContext';
|
||||
warnInvalidHookAccess();
|
||||
mountHookTypesDev();
|
||||
return readContext(context, observedBits);
|
||||
return mountContext(context, observedBits);
|
||||
},
|
||||
useEffect(
|
||||
create: () => (() => void) | void,
|
||||
@@ -1559,7 +1417,6 @@ if (__DEV__) {
|
||||
): void {
|
||||
currentHookNameInDev = 'useEffect';
|
||||
warnInvalidHookAccess();
|
||||
mountHookTypesDev();
|
||||
return mountEffect(create, deps);
|
||||
},
|
||||
useImperativeHandle<T>(
|
||||
@@ -1569,7 +1426,6 @@ if (__DEV__) {
|
||||
): void {
|
||||
currentHookNameInDev = 'useImperativeHandle';
|
||||
warnInvalidHookAccess();
|
||||
mountHookTypesDev();
|
||||
return mountImperativeHandle(ref, create, deps);
|
||||
},
|
||||
useLayoutEffect(
|
||||
@@ -1578,13 +1434,11 @@ if (__DEV__) {
|
||||
): void {
|
||||
currentHookNameInDev = 'useLayoutEffect';
|
||||
warnInvalidHookAccess();
|
||||
mountHookTypesDev();
|
||||
return mountLayoutEffect(create, deps);
|
||||
},
|
||||
useMemo<T>(create: () => T, deps: Array<mixed> | void | null): T {
|
||||
currentHookNameInDev = 'useMemo';
|
||||
warnInvalidHookAccess();
|
||||
mountHookTypesDev();
|
||||
const prevDispatcher = ReactCurrentDispatcher.current;
|
||||
ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnMountInDEV;
|
||||
try {
|
||||
@@ -1600,7 +1454,6 @@ if (__DEV__) {
|
||||
): [S, Dispatch<A>] {
|
||||
currentHookNameInDev = 'useReducer';
|
||||
warnInvalidHookAccess();
|
||||
mountHookTypesDev();
|
||||
const prevDispatcher = ReactCurrentDispatcher.current;
|
||||
ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnMountInDEV;
|
||||
try {
|
||||
@@ -1612,7 +1465,6 @@ if (__DEV__) {
|
||||
useRef<T>(initialValue: T): {current: T} {
|
||||
currentHookNameInDev = 'useRef';
|
||||
warnInvalidHookAccess();
|
||||
mountHookTypesDev();
|
||||
return mountRef(initialValue);
|
||||
},
|
||||
useState<S>(
|
||||
@@ -1620,7 +1472,6 @@ if (__DEV__) {
|
||||
): [S, Dispatch<BasicStateAction<S>>] {
|
||||
currentHookNameInDev = 'useState';
|
||||
warnInvalidHookAccess();
|
||||
mountHookTypesDev();
|
||||
const prevDispatcher = ReactCurrentDispatcher.current;
|
||||
ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnMountInDEV;
|
||||
try {
|
||||
@@ -1632,7 +1483,6 @@ if (__DEV__) {
|
||||
useDebugValue<T>(value: T, formatterFn: ?(value: T) => mixed): void {
|
||||
currentHookNameInDev = 'useDebugValue';
|
||||
warnInvalidHookAccess();
|
||||
mountHookTypesDev();
|
||||
return mountDebugValue(value, formatterFn);
|
||||
},
|
||||
};
|
||||
@@ -1649,7 +1499,6 @@ if (__DEV__) {
|
||||
useCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
|
||||
currentHookNameInDev = 'useCallback';
|
||||
warnInvalidHookAccess();
|
||||
updateHookTypesDev();
|
||||
return updateCallback(callback, deps);
|
||||
},
|
||||
useContext<T>(
|
||||
@@ -1658,8 +1507,7 @@ if (__DEV__) {
|
||||
): T {
|
||||
currentHookNameInDev = 'useContext';
|
||||
warnInvalidHookAccess();
|
||||
updateHookTypesDev();
|
||||
return readContext(context, observedBits);
|
||||
return updateContext(context, observedBits);
|
||||
},
|
||||
useEffect(
|
||||
create: () => (() => void) | void,
|
||||
@@ -1667,7 +1515,6 @@ if (__DEV__) {
|
||||
): void {
|
||||
currentHookNameInDev = 'useEffect';
|
||||
warnInvalidHookAccess();
|
||||
updateHookTypesDev();
|
||||
return updateEffect(create, deps);
|
||||
},
|
||||
useImperativeHandle<T>(
|
||||
@@ -1677,7 +1524,6 @@ if (__DEV__) {
|
||||
): void {
|
||||
currentHookNameInDev = 'useImperativeHandle';
|
||||
warnInvalidHookAccess();
|
||||
updateHookTypesDev();
|
||||
return updateImperativeHandle(ref, create, deps);
|
||||
},
|
||||
useLayoutEffect(
|
||||
@@ -1686,13 +1532,11 @@ if (__DEV__) {
|
||||
): void {
|
||||
currentHookNameInDev = 'useLayoutEffect';
|
||||
warnInvalidHookAccess();
|
||||
updateHookTypesDev();
|
||||
return updateLayoutEffect(create, deps);
|
||||
},
|
||||
useMemo<T>(create: () => T, deps: Array<mixed> | void | null): T {
|
||||
currentHookNameInDev = 'useMemo';
|
||||
warnInvalidHookAccess();
|
||||
updateHookTypesDev();
|
||||
const prevDispatcher = ReactCurrentDispatcher.current;
|
||||
ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnUpdateInDEV;
|
||||
try {
|
||||
@@ -1708,7 +1552,6 @@ if (__DEV__) {
|
||||
): [S, Dispatch<A>] {
|
||||
currentHookNameInDev = 'useReducer';
|
||||
warnInvalidHookAccess();
|
||||
updateHookTypesDev();
|
||||
const prevDispatcher = ReactCurrentDispatcher.current;
|
||||
ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnUpdateInDEV;
|
||||
try {
|
||||
@@ -1720,7 +1563,6 @@ if (__DEV__) {
|
||||
useRef<T>(initialValue: T): {current: T} {
|
||||
currentHookNameInDev = 'useRef';
|
||||
warnInvalidHookAccess();
|
||||
updateHookTypesDev();
|
||||
return updateRef(initialValue);
|
||||
},
|
||||
useState<S>(
|
||||
@@ -1728,7 +1570,6 @@ if (__DEV__) {
|
||||
): [S, Dispatch<BasicStateAction<S>>] {
|
||||
currentHookNameInDev = 'useState';
|
||||
warnInvalidHookAccess();
|
||||
updateHookTypesDev();
|
||||
const prevDispatcher = ReactCurrentDispatcher.current;
|
||||
ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnUpdateInDEV;
|
||||
try {
|
||||
@@ -1740,7 +1581,6 @@ if (__DEV__) {
|
||||
useDebugValue<T>(value: T, formatterFn: ?(value: T) => mixed): void {
|
||||
currentHookNameInDev = 'useDebugValue';
|
||||
warnInvalidHookAccess();
|
||||
updateHookTypesDev();
|
||||
return updateDebugValue(value, formatterFn);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -12,18 +12,11 @@ import type {
|
||||
Instance,
|
||||
TextInstance,
|
||||
HydratableInstance,
|
||||
SuspenseInstance,
|
||||
Container,
|
||||
HostContext,
|
||||
} from './ReactFiberHostConfig';
|
||||
|
||||
import {
|
||||
HostComponent,
|
||||
HostText,
|
||||
HostRoot,
|
||||
SuspenseComponent,
|
||||
DehydratedSuspenseComponent,
|
||||
} from 'shared/ReactWorkTags';
|
||||
import {HostComponent, HostText, HostRoot} from 'shared/ReactWorkTags';
|
||||
import {Deletion, Placement} from 'shared/ReactSideEffectTags';
|
||||
import invariant from 'shared/invariant';
|
||||
|
||||
@@ -33,24 +26,19 @@ import {
|
||||
supportsHydration,
|
||||
canHydrateInstance,
|
||||
canHydrateTextInstance,
|
||||
canHydrateSuspenseInstance,
|
||||
getNextHydratableSibling,
|
||||
getFirstHydratableChild,
|
||||
hydrateInstance,
|
||||
hydrateTextInstance,
|
||||
getNextHydratableInstanceAfterSuspenseInstance,
|
||||
didNotMatchHydratedContainerTextInstance,
|
||||
didNotMatchHydratedTextInstance,
|
||||
didNotHydrateContainerInstance,
|
||||
didNotHydrateInstance,
|
||||
didNotFindHydratableContainerInstance,
|
||||
didNotFindHydratableContainerTextInstance,
|
||||
didNotFindHydratableContainerSuspenseInstance,
|
||||
didNotFindHydratableInstance,
|
||||
didNotFindHydratableTextInstance,
|
||||
didNotFindHydratableSuspenseInstance,
|
||||
} from './ReactFiberHostConfig';
|
||||
import {enableSuspenseServerRenderer} from 'shared/ReactFeatureFlags';
|
||||
|
||||
// The deepest Fiber on the stack involved in a hydration context.
|
||||
// This may have been an insertion or a hydration.
|
||||
@@ -70,20 +58,6 @@ function enterHydrationState(fiber: Fiber): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
function reenterHydrationStateFromDehydratedSuspenseInstance(
|
||||
fiber: Fiber,
|
||||
): boolean {
|
||||
if (!supportsHydration) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const suspenseInstance = fiber.stateNode;
|
||||
nextHydratableInstance = getNextHydratableSibling(suspenseInstance);
|
||||
popToNextHostParent(fiber);
|
||||
isHydrating = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
function deleteHydratableInstance(
|
||||
returnFiber: Fiber,
|
||||
instance: HydratableInstance,
|
||||
@@ -141,9 +115,6 @@ function insertNonHydratedInstance(returnFiber: Fiber, fiber: Fiber) {
|
||||
const text = fiber.pendingProps;
|
||||
didNotFindHydratableContainerTextInstance(parentContainer, text);
|
||||
break;
|
||||
case SuspenseComponent:
|
||||
didNotFindHydratableContainerSuspenseInstance(parentContainer);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -172,13 +143,6 @@ function insertNonHydratedInstance(returnFiber: Fiber, fiber: Fiber) {
|
||||
text,
|
||||
);
|
||||
break;
|
||||
case SuspenseComponent:
|
||||
didNotFindHydratableSuspenseInstance(
|
||||
parentType,
|
||||
parentProps,
|
||||
parentInstance,
|
||||
);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -209,18 +173,6 @@ function tryHydrate(fiber, nextInstance) {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
case SuspenseComponent: {
|
||||
if (enableSuspenseServerRenderer) {
|
||||
const suspenseInstance = canHydrateSuspenseInstance(nextInstance);
|
||||
if (suspenseInstance !== null) {
|
||||
// Downgrade the tag to a dehydrated component until we've hydrated it.
|
||||
fiber.tag = DehydratedSuspenseComponent;
|
||||
fiber.stateNode = (suspenseInstance: SuspenseInstance);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -344,32 +296,12 @@ function prepareToHydrateHostTextInstance(fiber: Fiber): boolean {
|
||||
return shouldUpdate;
|
||||
}
|
||||
|
||||
function skipPastDehydratedSuspenseInstance(fiber: Fiber): void {
|
||||
if (!supportsHydration) {
|
||||
invariant(
|
||||
false,
|
||||
'Expected skipPastDehydratedSuspenseInstance() to never be called. ' +
|
||||
'This error is likely caused by a bug in React. Please file an issue.',
|
||||
);
|
||||
}
|
||||
let suspenseInstance = fiber.stateNode;
|
||||
invariant(
|
||||
suspenseInstance,
|
||||
'Expected to have a hydrated suspense instance. ' +
|
||||
'This error is likely caused by a bug in React. Please file an issue.',
|
||||
);
|
||||
nextHydratableInstance = getNextHydratableInstanceAfterSuspenseInstance(
|
||||
suspenseInstance,
|
||||
);
|
||||
}
|
||||
|
||||
function popToNextHostParent(fiber: Fiber): void {
|
||||
let parent = fiber.return;
|
||||
while (
|
||||
parent !== null &&
|
||||
parent.tag !== HostComponent &&
|
||||
parent.tag !== HostRoot &&
|
||||
parent.tag !== DehydratedSuspenseComponent
|
||||
parent.tag !== HostRoot
|
||||
) {
|
||||
parent = parent.return;
|
||||
}
|
||||
@@ -433,11 +365,9 @@ function resetHydrationState(): void {
|
||||
|
||||
export {
|
||||
enterHydrationState,
|
||||
reenterHydrationStateFromDehydratedSuspenseInstance,
|
||||
resetHydrationState,
|
||||
tryToClaimNextHydratableInstance,
|
||||
prepareToHydrateHostInstance,
|
||||
prepareToHydrateHostTextInstance,
|
||||
skipPastDehydratedSuspenseInstance,
|
||||
popHydrationState,
|
||||
};
|
||||
|
||||
@@ -27,11 +27,7 @@ import warningWithoutStack from 'shared/warningWithoutStack';
|
||||
import {isPrimaryRenderer} from './ReactFiberHostConfig';
|
||||
import {createCursor, push, pop} from './ReactFiberStack';
|
||||
import MAX_SIGNED_31_BIT_INT from './maxSigned31BitInt';
|
||||
import {
|
||||
ContextProvider,
|
||||
ClassComponent,
|
||||
DehydratedSuspenseComponent,
|
||||
} from 'shared/ReactWorkTags';
|
||||
import {ContextProvider, ClassComponent} from 'shared/ReactWorkTags';
|
||||
|
||||
import invariant from 'shared/invariant';
|
||||
import warning from 'shared/warning';
|
||||
@@ -43,7 +39,6 @@ import {
|
||||
} from 'react-reconciler/src/ReactUpdateQueue';
|
||||
import {NoWork} from './ReactFiberExpirationTime';
|
||||
import {markWorkInProgressReceivedUpdate} from './ReactFiberBeginWork';
|
||||
import {enableSuspenseServerRenderer} from 'shared/ReactFeatureFlags';
|
||||
|
||||
const valueCursor: StackCursor<mixed> = createCursor(null);
|
||||
|
||||
@@ -155,37 +150,6 @@ export function calculateChangedBits<T>(
|
||||
}
|
||||
}
|
||||
|
||||
function scheduleWorkOnParentPath(
|
||||
parent: Fiber | null,
|
||||
renderExpirationTime: ExpirationTime,
|
||||
) {
|
||||
// Update the child expiration time of all the ancestors, including
|
||||
// the alternates.
|
||||
let node = parent;
|
||||
while (node !== null) {
|
||||
let alternate = node.alternate;
|
||||
if (node.childExpirationTime < renderExpirationTime) {
|
||||
node.childExpirationTime = renderExpirationTime;
|
||||
if (
|
||||
alternate !== null &&
|
||||
alternate.childExpirationTime < renderExpirationTime
|
||||
) {
|
||||
alternate.childExpirationTime = renderExpirationTime;
|
||||
}
|
||||
} else if (
|
||||
alternate !== null &&
|
||||
alternate.childExpirationTime < renderExpirationTime
|
||||
) {
|
||||
alternate.childExpirationTime = renderExpirationTime;
|
||||
} else {
|
||||
// Neither alternate was updated, which means the rest of the
|
||||
// ancestor path already has sufficient priority.
|
||||
break;
|
||||
}
|
||||
node = node.return;
|
||||
}
|
||||
}
|
||||
|
||||
export function propagateContextChange(
|
||||
workInProgress: Fiber,
|
||||
context: ReactContext<mixed>,
|
||||
@@ -235,8 +199,31 @@ export function propagateContextChange(
|
||||
) {
|
||||
alternate.expirationTime = renderExpirationTime;
|
||||
}
|
||||
|
||||
scheduleWorkOnParentPath(fiber.return, renderExpirationTime);
|
||||
// Update the child expiration time of all the ancestors, including
|
||||
// the alternates.
|
||||
let node = fiber.return;
|
||||
while (node !== null) {
|
||||
alternate = node.alternate;
|
||||
if (node.childExpirationTime < renderExpirationTime) {
|
||||
node.childExpirationTime = renderExpirationTime;
|
||||
if (
|
||||
alternate !== null &&
|
||||
alternate.childExpirationTime < renderExpirationTime
|
||||
) {
|
||||
alternate.childExpirationTime = renderExpirationTime;
|
||||
}
|
||||
} else if (
|
||||
alternate !== null &&
|
||||
alternate.childExpirationTime < renderExpirationTime
|
||||
) {
|
||||
alternate.childExpirationTime = renderExpirationTime;
|
||||
} else {
|
||||
// Neither alternate was updated, which means the rest of the
|
||||
// ancestor path already has sufficient priority.
|
||||
break;
|
||||
}
|
||||
node = node.return;
|
||||
}
|
||||
|
||||
// Mark the expiration time on the list, too.
|
||||
if (list.expirationTime < renderExpirationTime) {
|
||||
@@ -252,29 +239,6 @@ export function propagateContextChange(
|
||||
} else if (fiber.tag === ContextProvider) {
|
||||
// Don't scan deeper if this is a matching provider
|
||||
nextFiber = fiber.type === workInProgress.type ? null : fiber.child;
|
||||
} else if (
|
||||
enableSuspenseServerRenderer &&
|
||||
fiber.tag === DehydratedSuspenseComponent
|
||||
) {
|
||||
// If a dehydrated suspense component is in this subtree, we don't know
|
||||
// if it will have any context consumers in it. The best we can do is
|
||||
// mark it as having updates on its children.
|
||||
if (fiber.expirationTime < renderExpirationTime) {
|
||||
fiber.expirationTime = renderExpirationTime;
|
||||
}
|
||||
let alternate = fiber.alternate;
|
||||
if (
|
||||
alternate !== null &&
|
||||
alternate.expirationTime < renderExpirationTime
|
||||
) {
|
||||
alternate.expirationTime = renderExpirationTime;
|
||||
}
|
||||
// This is intentionally passing this fiber as the parent
|
||||
// because we want to schedule this fiber as having work
|
||||
// on its children. We'll use the childExpirationTime on
|
||||
// this fiber to indicate that a context has changed.
|
||||
scheduleWorkOnParentPath(fiber, renderExpirationTime);
|
||||
nextFiber = fiber.sibling;
|
||||
} else {
|
||||
// Traverse down.
|
||||
nextFiber = fiber.child;
|
||||
|
||||
182
packages/react-reconciler/src/ReactFiberScheduler.js
vendored
182
packages/react-reconciler/src/ReactFiberScheduler.js
vendored
@@ -15,18 +15,8 @@ import type {Interaction} from 'scheduler/src/Tracing';
|
||||
import {
|
||||
__interactionsRef,
|
||||
__subscriberRef,
|
||||
unstable_wrap as Scheduler_tracing_wrap,
|
||||
unstable_wrap as Schedule_tracing_wrap,
|
||||
} from 'scheduler/tracing';
|
||||
import {
|
||||
unstable_next as Scheduler_next,
|
||||
unstable_getCurrentPriorityLevel as getCurrentPriorityLevel,
|
||||
unstable_runWithPriority as runWithPriority,
|
||||
unstable_ImmediatePriority as ImmediatePriority,
|
||||
unstable_UserBlockingPriority as UserBlockingPriority,
|
||||
unstable_NormalPriority as NormalPriority,
|
||||
unstable_LowPriority as LowPriority,
|
||||
unstable_IdlePriority as IdlePriority,
|
||||
} from 'scheduler';
|
||||
import {
|
||||
invokeGuardedCallback,
|
||||
hasCaughtError,
|
||||
@@ -60,8 +50,6 @@ import {
|
||||
HostRoot,
|
||||
MemoComponent,
|
||||
SimpleMemoComponent,
|
||||
SuspenseComponent,
|
||||
DehydratedSuspenseComponent,
|
||||
} from 'shared/ReactWorkTags';
|
||||
import {
|
||||
enableSchedulerTracing,
|
||||
@@ -69,7 +57,6 @@ import {
|
||||
enableUserTimingAPI,
|
||||
replayFailedUnitOfWorkWithInvokeGuardedCallback,
|
||||
warnAboutDeprecatedLifecycles,
|
||||
enableSuspenseServerRenderer,
|
||||
} from 'shared/ReactFeatureFlags';
|
||||
import getComponentName from 'shared/getComponentName';
|
||||
import invariant from 'shared/invariant';
|
||||
@@ -135,7 +122,7 @@ import {
|
||||
computeAsyncExpiration,
|
||||
computeInteractiveExpiration,
|
||||
} from './ReactFiberExpirationTime';
|
||||
import {ConcurrentMode, ProfileMode, NoContext} from './ReactTypeOfMode';
|
||||
import {ConcurrentMode, ProfileMode} from './ReactTypeOfMode';
|
||||
import {enqueueUpdate, resetCurrentlyProcessingQueue} from './ReactUpdateQueue';
|
||||
import {createCapturedValue} from './ReactCapturedValue';
|
||||
import {
|
||||
@@ -255,6 +242,11 @@ if (__DEV__) {
|
||||
// Used to ensure computeUniqueAsyncExpiration is monotonically decreasing.
|
||||
let lastUniqueAsyncExpiration: number = Sync - 1;
|
||||
|
||||
// Represents the expiration time that incoming updates should use. (If this
|
||||
// is NoWork, use the default strategy: async updates in async mode, sync
|
||||
// updates in sync mode.)
|
||||
let expirationContext: ExpirationTime = NoWork;
|
||||
|
||||
let isWorking: boolean = false;
|
||||
|
||||
// The next work in progress fiber that we're currently working on.
|
||||
@@ -575,10 +567,6 @@ function commitPassiveEffects(root: FiberRoot, firstEffect: Fiber): void {
|
||||
if (rootExpirationTime !== NoWork) {
|
||||
requestWork(root, rootExpirationTime);
|
||||
}
|
||||
// Flush any sync work that was scheduled by effects
|
||||
if (!isBatchingUpdates && !isRendering) {
|
||||
performSyncWork();
|
||||
}
|
||||
}
|
||||
|
||||
function isAlreadyFailedLegacyErrorBoundary(instance: mixed): boolean {
|
||||
@@ -805,11 +793,9 @@ function commitRoot(root: FiberRoot, finishedWork: Fiber): void {
|
||||
// TODO: Avoid this extra callback by mutating the tracing ref directly,
|
||||
// like we do at the beginning of commitRoot. I've opted not to do that
|
||||
// here because that code is still in flux.
|
||||
callback = Scheduler_tracing_wrap(callback);
|
||||
callback = Schedule_tracing_wrap(callback);
|
||||
}
|
||||
passiveEffectCallbackHandle = runWithPriority(NormalPriority, () => {
|
||||
return schedulePassiveEffects(callback);
|
||||
});
|
||||
passiveEffectCallbackHandle = schedulePassiveEffects(callback);
|
||||
passiveEffectCallback = callback;
|
||||
}
|
||||
|
||||
@@ -1462,7 +1448,7 @@ function renderRoot(root: FiberRoot, isYieldy: boolean): void {
|
||||
return;
|
||||
} else if (
|
||||
// There's no lower priority work, but we're rendering asynchronously.
|
||||
// Synchronously attempt to render the same level one more time. This is
|
||||
// Synchronsouly attempt to render the same level one more time. This is
|
||||
// similar to a suspend, but without a timeout because we're not waiting
|
||||
// for a promise to resolve.
|
||||
!root.didError &&
|
||||
@@ -1593,58 +1579,52 @@ function computeUniqueAsyncExpiration(): ExpirationTime {
|
||||
}
|
||||
|
||||
function computeExpirationForFiber(currentTime: ExpirationTime, fiber: Fiber) {
|
||||
const priorityLevel = getCurrentPriorityLevel();
|
||||
|
||||
let expirationTime;
|
||||
if ((fiber.mode & ConcurrentMode) === NoContext) {
|
||||
// Outside of concurrent mode, updates are always synchronous.
|
||||
expirationTime = Sync;
|
||||
} else if (isWorking && !isCommitting) {
|
||||
// During render phase, updates expire during as the current render.
|
||||
expirationTime = nextRenderExpirationTime;
|
||||
if (expirationContext !== NoWork) {
|
||||
// An explicit expiration context was set;
|
||||
expirationTime = expirationContext;
|
||||
} else if (isWorking) {
|
||||
if (isCommitting) {
|
||||
// Updates that occur during the commit phase should have sync priority
|
||||
// by default.
|
||||
expirationTime = Sync;
|
||||
} else {
|
||||
// Updates during the render phase should expire at the same time as
|
||||
// the work that is being rendered.
|
||||
expirationTime = nextRenderExpirationTime;
|
||||
}
|
||||
} else {
|
||||
switch (priorityLevel) {
|
||||
case ImmediatePriority:
|
||||
expirationTime = Sync;
|
||||
break;
|
||||
case UserBlockingPriority:
|
||||
// No explicit expiration context was set, and we're not currently
|
||||
// performing work. Calculate a new expiration time.
|
||||
if (fiber.mode & ConcurrentMode) {
|
||||
if (isBatchingInteractiveUpdates) {
|
||||
// This is an interactive update
|
||||
expirationTime = computeInteractiveExpiration(currentTime);
|
||||
break;
|
||||
case NormalPriority:
|
||||
// This is a normal, concurrent update
|
||||
} else {
|
||||
// This is an async update
|
||||
expirationTime = computeAsyncExpiration(currentTime);
|
||||
break;
|
||||
case LowPriority:
|
||||
case IdlePriority:
|
||||
expirationTime = Never;
|
||||
break;
|
||||
default:
|
||||
invariant(
|
||||
false,
|
||||
'Unknown priority level. This error is likely caused by a bug in ' +
|
||||
'React. Please file an issue.',
|
||||
);
|
||||
}
|
||||
|
||||
// If we're in the middle of rendering a tree, do not update at the same
|
||||
// expiration time that is already rendering.
|
||||
if (nextRoot !== null && expirationTime === nextRenderExpirationTime) {
|
||||
expirationTime -= 1;
|
||||
}
|
||||
// If we're in the middle of rendering a tree, do not update at the same
|
||||
// expiration time that is already rendering.
|
||||
if (nextRoot !== null && expirationTime === nextRenderExpirationTime) {
|
||||
expirationTime -= 1;
|
||||
}
|
||||
} else {
|
||||
// This is a sync update
|
||||
expirationTime = Sync;
|
||||
}
|
||||
}
|
||||
|
||||
// Keep track of the lowest pending interactive expiration time. This
|
||||
// allows us to synchronously flush all interactive updates
|
||||
// when needed.
|
||||
// TODO: Move this to renderer?
|
||||
if (
|
||||
priorityLevel === UserBlockingPriority &&
|
||||
(lowestPriorityPendingInteractiveExpirationTime === NoWork ||
|
||||
expirationTime < lowestPriorityPendingInteractiveExpirationTime)
|
||||
) {
|
||||
lowestPriorityPendingInteractiveExpirationTime = expirationTime;
|
||||
if (isBatchingInteractiveUpdates) {
|
||||
// This is an interactive update. Keep track of the lowest pending
|
||||
// interactive expiration time. This allows us to synchronously flush
|
||||
// all interactive updates when needed.
|
||||
if (
|
||||
lowestPriorityPendingInteractiveExpirationTime === NoWork ||
|
||||
expirationTime < lowestPriorityPendingInteractiveExpirationTime
|
||||
) {
|
||||
lowestPriorityPendingInteractiveExpirationTime = expirationTime;
|
||||
}
|
||||
}
|
||||
|
||||
return expirationTime;
|
||||
}
|
||||
|
||||
@@ -1704,25 +1684,8 @@ function retryTimedOutBoundary(boundaryFiber: Fiber, thenable: Thenable) {
|
||||
// resolved, which means at least part of the tree was likely unblocked. Try
|
||||
// rendering again, at a new expiration time.
|
||||
|
||||
let retryCache: WeakSet<Thenable> | Set<Thenable> | null;
|
||||
if (enableSuspenseServerRenderer) {
|
||||
switch (boundaryFiber.tag) {
|
||||
case SuspenseComponent:
|
||||
retryCache = boundaryFiber.stateNode;
|
||||
break;
|
||||
case DehydratedSuspenseComponent:
|
||||
retryCache = boundaryFiber.memoizedState;
|
||||
break;
|
||||
default:
|
||||
invariant(
|
||||
false,
|
||||
'Pinged unknown suspense boundary type. ' +
|
||||
'This is probably a bug in React.',
|
||||
);
|
||||
}
|
||||
} else {
|
||||
retryCache = boundaryFiber.stateNode;
|
||||
}
|
||||
const retryCache: WeakSet<Thenable> | Set<Thenable> | null =
|
||||
boundaryFiber.stateNode;
|
||||
if (retryCache !== null) {
|
||||
// The thenable resolved, so we no longer need to memoize, because it will
|
||||
// never be thrown again.
|
||||
@@ -1839,10 +1802,8 @@ export function warnIfNotCurrentlyBatchingInDev(fiber: Fiber): void {
|
||||
'});\n' +
|
||||
'/* assert on the output */\n\n' +
|
||||
"This ensures that you're testing the behavior the user would see in the browser." +
|
||||
' Learn more at https://fb.me/react-wrap-tests-with-act' +
|
||||
'%s',
|
||||
' Learn more at https://fb.me/react-wrap-tests-with-act',
|
||||
getComponentName(fiber.type),
|
||||
getStackByFiberInDevAndProd(fiber),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1901,6 +1862,20 @@ function scheduleWork(fiber: Fiber, expirationTime: ExpirationTime) {
|
||||
}
|
||||
}
|
||||
|
||||
function deferredUpdates<A>(fn: () => A): A {
|
||||
const currentTime = requestCurrentTime();
|
||||
const previousExpirationContext = expirationContext;
|
||||
const previousIsBatchingInteractiveUpdates = isBatchingInteractiveUpdates;
|
||||
expirationContext = computeAsyncExpiration(currentTime);
|
||||
isBatchingInteractiveUpdates = false;
|
||||
try {
|
||||
return fn();
|
||||
} finally {
|
||||
expirationContext = previousExpirationContext;
|
||||
isBatchingInteractiveUpdates = previousIsBatchingInteractiveUpdates;
|
||||
}
|
||||
}
|
||||
|
||||
function syncUpdates<A, B, C0, D, R>(
|
||||
fn: (A, B, C0, D) => R,
|
||||
a: A,
|
||||
@@ -1908,9 +1883,13 @@ function syncUpdates<A, B, C0, D, R>(
|
||||
c: C0,
|
||||
d: D,
|
||||
): R {
|
||||
return runWithPriority(ImmediatePriority, () => {
|
||||
const previousExpirationContext = expirationContext;
|
||||
expirationContext = Sync;
|
||||
try {
|
||||
return fn(a, b, c, d);
|
||||
});
|
||||
} finally {
|
||||
expirationContext = previousExpirationContext;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Everything below this is written as if it has been lifted to the
|
||||
@@ -1931,6 +1910,7 @@ let unhandledError: mixed | null = null;
|
||||
|
||||
let isBatchingUpdates: boolean = false;
|
||||
let isUnbatchingUpdates: boolean = false;
|
||||
let isBatchingInteractiveUpdates: boolean = false;
|
||||
|
||||
let completedBatches: Array<Batch> | null = null;
|
||||
|
||||
@@ -2461,9 +2441,7 @@ function completeRoot(
|
||||
lastCommittedRootDuringThisBatch = root;
|
||||
nestedUpdateCount = 0;
|
||||
}
|
||||
runWithPriority(ImmediatePriority, () => {
|
||||
commitRoot(root, finishedWork);
|
||||
});
|
||||
commitRoot(root, finishedWork);
|
||||
}
|
||||
|
||||
function onUncaughtError(error: mixed) {
|
||||
@@ -2529,6 +2507,9 @@ function flushSync<A, R>(fn: (a: A) => R, a: A): R {
|
||||
}
|
||||
|
||||
function interactiveUpdates<A, B, R>(fn: (A, B) => R, a: A, b: B): R {
|
||||
if (isBatchingInteractiveUpdates) {
|
||||
return fn(a, b);
|
||||
}
|
||||
// If there are any pending interactive updates, synchronously flush them.
|
||||
// This needs to happen before we read any handlers, because the effect of
|
||||
// the previous event may influence which handlers are called during
|
||||
@@ -2542,13 +2523,14 @@ function interactiveUpdates<A, B, R>(fn: (A, B) => R, a: A, b: B): R {
|
||||
performWork(lowestPriorityPendingInteractiveExpirationTime, false);
|
||||
lowestPriorityPendingInteractiveExpirationTime = NoWork;
|
||||
}
|
||||
const previousIsBatchingInteractiveUpdates = isBatchingInteractiveUpdates;
|
||||
const previousIsBatchingUpdates = isBatchingUpdates;
|
||||
isBatchingInteractiveUpdates = true;
|
||||
isBatchingUpdates = true;
|
||||
try {
|
||||
return runWithPriority(UserBlockingPriority, () => {
|
||||
return fn(a, b);
|
||||
});
|
||||
return fn(a, b);
|
||||
} finally {
|
||||
isBatchingInteractiveUpdates = previousIsBatchingInteractiveUpdates;
|
||||
isBatchingUpdates = previousIsBatchingUpdates;
|
||||
if (!isBatchingUpdates && !isRendering) {
|
||||
performSyncWork();
|
||||
@@ -2598,7 +2580,7 @@ export {
|
||||
unbatchedUpdates,
|
||||
flushSync,
|
||||
flushControlled,
|
||||
Scheduler_next as deferredUpdates,
|
||||
deferredUpdates,
|
||||
syncUpdates,
|
||||
interactiveUpdates,
|
||||
flushInteractiveUpdates,
|
||||
|
||||
@@ -25,7 +25,6 @@ import {
|
||||
HostPortal,
|
||||
ContextProvider,
|
||||
SuspenseComponent,
|
||||
DehydratedSuspenseComponent,
|
||||
IncompleteClassComponent,
|
||||
} from 'shared/ReactWorkTags';
|
||||
import {
|
||||
@@ -35,10 +34,7 @@ import {
|
||||
ShouldCapture,
|
||||
LifecycleEffectMask,
|
||||
} from 'shared/ReactSideEffectTags';
|
||||
import {
|
||||
enableSchedulerTracing,
|
||||
enableSuspenseServerRenderer,
|
||||
} from 'shared/ReactFeatureFlags';
|
||||
import {enableSchedulerTracing} from 'shared/ReactFeatureFlags';
|
||||
import {ConcurrentMode} from './ReactTypeOfMode';
|
||||
import {shouldCaptureSuspense} from './ReactFiberSuspenseComponent';
|
||||
|
||||
@@ -66,7 +62,6 @@ import {
|
||||
markLegacyErrorBoundaryAsFailed,
|
||||
isAlreadyFailedLegacyErrorBoundary,
|
||||
pingSuspendedRoot,
|
||||
retryTimedOutBoundary,
|
||||
} from './ReactFiberScheduler';
|
||||
|
||||
import invariant from 'shared/invariant';
|
||||
@@ -78,7 +73,6 @@ import {
|
||||
} from './ReactFiberExpirationTime';
|
||||
import {findEarliestOutstandingPriorityLevel} from './ReactFiberPendingPriority';
|
||||
|
||||
const PossiblyWeakSet = typeof WeakSet === 'function' ? WeakSet : Set;
|
||||
const PossiblyWeakMap = typeof WeakMap === 'function' ? WeakMap : Map;
|
||||
|
||||
function createRootErrorUpdate(
|
||||
@@ -150,43 +144,6 @@ function createClassErrorUpdate(
|
||||
return update;
|
||||
}
|
||||
|
||||
function attachPingListener(
|
||||
root: FiberRoot,
|
||||
renderExpirationTime: ExpirationTime,
|
||||
thenable: Thenable,
|
||||
) {
|
||||
// Attach a listener to the promise to "ping" the root and retry. But
|
||||
// only if one does not already exist for the current render expiration
|
||||
// time (which acts like a "thread ID" here).
|
||||
let pingCache = root.pingCache;
|
||||
let threadIDs;
|
||||
if (pingCache === null) {
|
||||
pingCache = root.pingCache = new PossiblyWeakMap();
|
||||
threadIDs = new Set();
|
||||
pingCache.set(thenable, threadIDs);
|
||||
} else {
|
||||
threadIDs = pingCache.get(thenable);
|
||||
if (threadIDs === undefined) {
|
||||
threadIDs = new Set();
|
||||
pingCache.set(thenable, threadIDs);
|
||||
}
|
||||
}
|
||||
if (!threadIDs.has(renderExpirationTime)) {
|
||||
// Memoize using the thread ID to prevent redundant listeners.
|
||||
threadIDs.add(renderExpirationTime);
|
||||
let ping = pingSuspendedRoot.bind(
|
||||
null,
|
||||
root,
|
||||
thenable,
|
||||
renderExpirationTime,
|
||||
);
|
||||
if (enableSchedulerTracing) {
|
||||
ping = Schedule_tracing_wrap(ping);
|
||||
}
|
||||
thenable.then(ping, ping);
|
||||
}
|
||||
}
|
||||
|
||||
function throwException(
|
||||
root: FiberRoot,
|
||||
returnFiber: Fiber,
|
||||
@@ -241,9 +198,6 @@ function throwException(
|
||||
}
|
||||
}
|
||||
}
|
||||
// If there is a DehydratedSuspenseComponent we don't have to do anything because
|
||||
// if something suspends inside it, we will simply leave that as dehydrated. It
|
||||
// will never timeout.
|
||||
workInProgress = workInProgress.return;
|
||||
} while (workInProgress !== null);
|
||||
|
||||
@@ -311,7 +265,36 @@ function throwException(
|
||||
// Confirmed that the boundary is in a concurrent mode tree. Continue
|
||||
// with the normal suspend path.
|
||||
|
||||
attachPingListener(root, renderExpirationTime, thenable);
|
||||
// Attach a listener to the promise to "ping" the root and retry. But
|
||||
// only if one does not already exist for the current render expiration
|
||||
// time (which acts like a "thread ID" here).
|
||||
let pingCache = root.pingCache;
|
||||
let threadIDs;
|
||||
if (pingCache === null) {
|
||||
pingCache = root.pingCache = new PossiblyWeakMap();
|
||||
threadIDs = new Set();
|
||||
pingCache.set(thenable, threadIDs);
|
||||
} else {
|
||||
threadIDs = pingCache.get(thenable);
|
||||
if (threadIDs === undefined) {
|
||||
threadIDs = new Set();
|
||||
pingCache.set(thenable, threadIDs);
|
||||
}
|
||||
}
|
||||
if (!threadIDs.has(renderExpirationTime)) {
|
||||
// Memoize using the thread ID to prevent redundant listeners.
|
||||
threadIDs.add(renderExpirationTime);
|
||||
let ping = pingSuspendedRoot.bind(
|
||||
null,
|
||||
root,
|
||||
thenable,
|
||||
renderExpirationTime,
|
||||
);
|
||||
if (enableSchedulerTracing) {
|
||||
ping = Schedule_tracing_wrap(ping);
|
||||
}
|
||||
thenable.then(ping, ping);
|
||||
}
|
||||
|
||||
let absoluteTimeoutMs;
|
||||
if (earliestTimeoutMs === -1) {
|
||||
@@ -348,40 +331,6 @@ function throwException(
|
||||
// whole tree.
|
||||
renderDidSuspend(root, absoluteTimeoutMs, renderExpirationTime);
|
||||
|
||||
workInProgress.effectTag |= ShouldCapture;
|
||||
workInProgress.expirationTime = renderExpirationTime;
|
||||
return;
|
||||
} else if (
|
||||
enableSuspenseServerRenderer &&
|
||||
workInProgress.tag === DehydratedSuspenseComponent
|
||||
) {
|
||||
attachPingListener(root, renderExpirationTime, thenable);
|
||||
|
||||
// Since we already have a current fiber, we can eagerly add a retry listener.
|
||||
let retryCache = workInProgress.memoizedState;
|
||||
if (retryCache === null) {
|
||||
retryCache = workInProgress.memoizedState = new PossiblyWeakSet();
|
||||
const current = workInProgress.alternate;
|
||||
invariant(
|
||||
current,
|
||||
'A dehydrated suspense boundary must commit before trying to render. ' +
|
||||
'This is probably a bug in React.',
|
||||
);
|
||||
current.memoizedState = retryCache;
|
||||
}
|
||||
// Memoize using the boundary fiber to prevent redundant listeners.
|
||||
if (!retryCache.has(thenable)) {
|
||||
retryCache.add(thenable);
|
||||
let retry = retryTimedOutBoundary.bind(
|
||||
null,
|
||||
workInProgress,
|
||||
thenable,
|
||||
);
|
||||
if (enableSchedulerTracing) {
|
||||
retry = Schedule_tracing_wrap(retry);
|
||||
}
|
||||
thenable.then(retry, retry);
|
||||
}
|
||||
workInProgress.effectTag |= ShouldCapture;
|
||||
workInProgress.expirationTime = renderExpirationTime;
|
||||
return;
|
||||
@@ -483,7 +432,6 @@ function unwindWork(
|
||||
return workInProgress;
|
||||
}
|
||||
case HostComponent: {
|
||||
// TODO: popHydrationState
|
||||
popHostContext(workInProgress);
|
||||
return null;
|
||||
}
|
||||
@@ -496,18 +444,6 @@ function unwindWork(
|
||||
}
|
||||
return null;
|
||||
}
|
||||
case DehydratedSuspenseComponent: {
|
||||
if (enableSuspenseServerRenderer) {
|
||||
// TODO: popHydrationState
|
||||
const effectTag = workInProgress.effectTag;
|
||||
if (effectTag & ShouldCapture) {
|
||||
workInProgress.effectTag = (effectTag & ~ShouldCapture) | DidCapture;
|
||||
// Captured a suspense effect. Re-render the boundary.
|
||||
return workInProgress;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
case HostPortal:
|
||||
popHostContainer(workInProgress);
|
||||
return null;
|
||||
|
||||
@@ -352,7 +352,7 @@ describe('ReactHooks', () => {
|
||||
]);
|
||||
expect(root).toMatchRenderedOutput('0 (light)');
|
||||
|
||||
// Updating the theme to the same value doesn't cause the consumers
|
||||
// Updating the theme to the same value does't cause the consumers
|
||||
// to re-render.
|
||||
setTheme('light');
|
||||
expect(root).toFlushAndYield([]);
|
||||
@@ -669,76 +669,6 @@ describe('ReactHooks', () => {
|
||||
}).toThrow('is not a function');
|
||||
});
|
||||
|
||||
it('does not forget render phase useState updates inside an effect', () => {
|
||||
const {useState, useEffect} = React;
|
||||
|
||||
function Counter() {
|
||||
const [counter, setCounter] = useState(0);
|
||||
if (counter === 0) {
|
||||
setCounter(x => x + 1);
|
||||
setCounter(x => x + 1);
|
||||
}
|
||||
useEffect(() => {
|
||||
setCounter(x => x + 1);
|
||||
setCounter(x => x + 1);
|
||||
}, []);
|
||||
return counter;
|
||||
}
|
||||
|
||||
const root = ReactTestRenderer.create(null);
|
||||
ReactTestRenderer.act(() => {
|
||||
root.update(<Counter />);
|
||||
});
|
||||
expect(root).toMatchRenderedOutput('4');
|
||||
});
|
||||
|
||||
it('does not forget render phase useReducer updates inside an effect with hoisted reducer', () => {
|
||||
const {useReducer, useEffect} = React;
|
||||
|
||||
const reducer = x => x + 1;
|
||||
function Counter() {
|
||||
const [counter, increment] = useReducer(reducer, 0);
|
||||
if (counter === 0) {
|
||||
increment();
|
||||
increment();
|
||||
}
|
||||
useEffect(() => {
|
||||
increment();
|
||||
increment();
|
||||
}, []);
|
||||
return counter;
|
||||
}
|
||||
|
||||
const root = ReactTestRenderer.create(null);
|
||||
ReactTestRenderer.act(() => {
|
||||
root.update(<Counter />);
|
||||
});
|
||||
expect(root).toMatchRenderedOutput('4');
|
||||
});
|
||||
|
||||
it('does not forget render phase useReducer updates inside an effect with inline reducer', () => {
|
||||
const {useReducer, useEffect} = React;
|
||||
|
||||
function Counter() {
|
||||
const [counter, increment] = useReducer(x => x + 1, 0);
|
||||
if (counter === 0) {
|
||||
increment();
|
||||
increment();
|
||||
}
|
||||
useEffect(() => {
|
||||
increment();
|
||||
increment();
|
||||
}, []);
|
||||
return counter;
|
||||
}
|
||||
|
||||
const root = ReactTestRenderer.create(null);
|
||||
ReactTestRenderer.act(() => {
|
||||
root.update(<Counter />);
|
||||
});
|
||||
expect(root).toMatchRenderedOutput('4');
|
||||
});
|
||||
|
||||
it('warns for bad useImperativeHandle first arg', () => {
|
||||
const {useImperativeHandle} = React;
|
||||
function App() {
|
||||
@@ -984,6 +914,8 @@ describe('ReactHooks', () => {
|
||||
it('warns when calling hooks inside useReducer', () => {
|
||||
const {useReducer, useState, useRef} = React;
|
||||
|
||||
spyOnDev(console, 'error');
|
||||
|
||||
function App() {
|
||||
const [value, dispatch] = useReducer((state, action) => {
|
||||
useRef(0);
|
||||
@@ -995,23 +927,16 @@ describe('ReactHooks', () => {
|
||||
useState();
|
||||
return value;
|
||||
}
|
||||
|
||||
expect(() => {
|
||||
expect(() => {
|
||||
ReactTestRenderer.create(<App />);
|
||||
}).toThrow('Rendered more hooks than during the previous render.');
|
||||
}).toWarnDev([
|
||||
'Do not call Hooks inside useEffect(...), useMemo(...), or other built-in Hooks',
|
||||
'Do not call Hooks inside useEffect(...), useMemo(...), or other built-in Hooks',
|
||||
'Warning: React has detected a change in the order of Hooks called by App. ' +
|
||||
'This will lead to bugs and errors if not fixed. For more information, ' +
|
||||
'read the Rules of Hooks: https://fb.me/rules-of-hooks\n\n' +
|
||||
' Previous render Next render\n' +
|
||||
' ------------------------------------------------------\n' +
|
||||
'1. useReducer useReducer\n' +
|
||||
'2. useState useRef\n' +
|
||||
' ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n',
|
||||
]);
|
||||
ReactTestRenderer.create(<App />);
|
||||
}).toThrow('Rendered more hooks than during the previous render.');
|
||||
|
||||
if (__DEV__) {
|
||||
expect(console.error).toHaveBeenCalledTimes(3);
|
||||
expect(console.error.calls.argsFor(0)[0]).toContain(
|
||||
'Do not call Hooks inside useEffect(...), useMemo(...), or other built-in Hooks',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it("warns when calling hooks inside useState's initialize function", () => {
|
||||
@@ -1342,261 +1267,74 @@ describe('ReactHooks', () => {
|
||||
expect(useMemoCount).toBe(__DEV__ ? 2 : 1); // Has Hooks
|
||||
});
|
||||
|
||||
describe('hook ordering', () => {
|
||||
const useCallbackHelper = () => React.useCallback(() => {}, []);
|
||||
const useContextHelper = () => React.useContext(React.createContext());
|
||||
const useDebugValueHelper = () => React.useDebugValue('abc');
|
||||
const useEffectHelper = () => React.useEffect(() => () => {}, []);
|
||||
const useImperativeHandleHelper = () => {
|
||||
React.useImperativeHandle({current: null}, () => ({}), []);
|
||||
};
|
||||
const useLayoutEffectHelper = () =>
|
||||
React.useLayoutEffect(() => () => {}, []);
|
||||
const useMemoHelper = () => React.useMemo(() => 123, []);
|
||||
const useReducerHelper = () => React.useReducer((s, a) => a, 0);
|
||||
const useRefHelper = () => React.useRef(null);
|
||||
const useStateHelper = () => React.useState(0);
|
||||
|
||||
// We don't include useImperativeHandleHelper in this set,
|
||||
// because it generates an additional warning about the inputs length changing.
|
||||
// We test it below with its own test.
|
||||
let orderedHooks = [
|
||||
useCallbackHelper,
|
||||
useContextHelper,
|
||||
useDebugValueHelper,
|
||||
useEffectHelper,
|
||||
useLayoutEffectHelper,
|
||||
useMemoHelper,
|
||||
useReducerHelper,
|
||||
useRefHelper,
|
||||
useStateHelper,
|
||||
];
|
||||
|
||||
const formatHookNamesToMatchErrorMessage = (hookNameA, hookNameB) => {
|
||||
return `use${hookNameA}${' '.repeat(24 - hookNameA.length)}${
|
||||
hookNameB ? `use${hookNameB}` : undefined
|
||||
}`;
|
||||
};
|
||||
|
||||
orderedHooks.forEach((firstHelper, index) => {
|
||||
const secondHelper =
|
||||
index > 0
|
||||
? orderedHooks[index - 1]
|
||||
: orderedHooks[orderedHooks.length - 1];
|
||||
|
||||
const hookNameA = firstHelper.name
|
||||
.replace('use', '')
|
||||
.replace('Helper', '');
|
||||
const hookNameB = secondHelper.name
|
||||
.replace('use', '')
|
||||
.replace('Helper', '');
|
||||
|
||||
it(`warns on using differently ordered hooks (${hookNameA}, ${hookNameB}) on subsequent renders`, () => {
|
||||
function App(props) {
|
||||
/* eslint-disable no-unused-vars */
|
||||
if (props.update) {
|
||||
secondHelper();
|
||||
firstHelper();
|
||||
} else {
|
||||
firstHelper();
|
||||
secondHelper();
|
||||
}
|
||||
// This should not appear in the warning message because it occurs after the first mismatch
|
||||
useRefHelper();
|
||||
return null;
|
||||
/* eslint-enable no-unused-vars */
|
||||
}
|
||||
let root = ReactTestRenderer.create(<App update={false} />);
|
||||
expect(() => {
|
||||
try {
|
||||
root.update(<App update={true} />);
|
||||
} catch (error) {
|
||||
// Swapping certain types of hooks will cause runtime errors.
|
||||
// This is okay as far as this test is concerned.
|
||||
// We just want to verify that warnings are always logged.
|
||||
}
|
||||
}).toWarnDev([
|
||||
'Warning: React has detected a change in the order of Hooks called by App. ' +
|
||||
'This will lead to bugs and errors if not fixed. For more information, ' +
|
||||
'read the Rules of Hooks: https://fb.me/rules-of-hooks\n\n' +
|
||||
' Previous render Next render\n' +
|
||||
' ------------------------------------------------------\n' +
|
||||
`1. ${formatHookNamesToMatchErrorMessage(hookNameA, hookNameB)}\n` +
|
||||
' ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n' +
|
||||
' in App (at **)',
|
||||
]);
|
||||
|
||||
// further warnings for this component are silenced
|
||||
try {
|
||||
root.update(<App update={false} />);
|
||||
} catch (error) {
|
||||
// Swapping certain types of hooks will cause runtime errors.
|
||||
// This is okay as far as this test is concerned.
|
||||
// We just want to verify that warnings are always logged.
|
||||
}
|
||||
});
|
||||
|
||||
it(`warns when more hooks (${(hookNameA,
|
||||
hookNameB)}) are used during update than mount`, () => {
|
||||
function App(props) {
|
||||
/* eslint-disable no-unused-vars */
|
||||
if (props.update) {
|
||||
firstHelper();
|
||||
secondHelper();
|
||||
} else {
|
||||
firstHelper();
|
||||
}
|
||||
return null;
|
||||
/* eslint-enable no-unused-vars */
|
||||
}
|
||||
let root = ReactTestRenderer.create(<App update={false} />);
|
||||
expect(() => {
|
||||
try {
|
||||
root.update(<App update={true} />);
|
||||
} catch (error) {
|
||||
// Swapping certain types of hooks will cause runtime errors.
|
||||
// This is okay as far as this test is concerned.
|
||||
// We just want to verify that warnings are always logged.
|
||||
}
|
||||
}).toWarnDev([
|
||||
'Warning: React has detected a change in the order of Hooks called by App. ' +
|
||||
'This will lead to bugs and errors if not fixed. For more information, ' +
|
||||
'read the Rules of Hooks: https://fb.me/rules-of-hooks\n\n' +
|
||||
' Previous render Next render\n' +
|
||||
' ------------------------------------------------------\n' +
|
||||
`1. ${formatHookNamesToMatchErrorMessage(hookNameA, hookNameA)}\n` +
|
||||
`2. undefined use${hookNameB}\n` +
|
||||
' ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n' +
|
||||
' in App (at **)',
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
// We don't include useContext or useDebugValue in this set,
|
||||
// because they aren't added to the hooks list and so won't throw.
|
||||
let hooksInList = [
|
||||
useCallbackHelper,
|
||||
useEffectHelper,
|
||||
useImperativeHandleHelper,
|
||||
useLayoutEffectHelper,
|
||||
useMemoHelper,
|
||||
useReducerHelper,
|
||||
useRefHelper,
|
||||
useStateHelper,
|
||||
];
|
||||
|
||||
hooksInList.forEach((firstHelper, index) => {
|
||||
const secondHelper =
|
||||
index > 0
|
||||
? hooksInList[index - 1]
|
||||
: hooksInList[hooksInList.length - 1];
|
||||
|
||||
const hookNameA = firstHelper.name
|
||||
.replace('use', '')
|
||||
.replace('Helper', '');
|
||||
const hookNameB = secondHelper.name
|
||||
.replace('use', '')
|
||||
.replace('Helper', '');
|
||||
|
||||
it(`warns when fewer hooks (${(hookNameA,
|
||||
hookNameB)}) are used during update than mount`, () => {
|
||||
function App(props) {
|
||||
/* eslint-disable no-unused-vars */
|
||||
if (props.update) {
|
||||
firstHelper();
|
||||
} else {
|
||||
firstHelper();
|
||||
secondHelper();
|
||||
}
|
||||
return null;
|
||||
/* eslint-enable no-unused-vars */
|
||||
}
|
||||
let root = ReactTestRenderer.create(<App update={false} />);
|
||||
expect(() => {
|
||||
root.update(<App update={true} />);
|
||||
}).toThrow('Rendered fewer hooks than expected.');
|
||||
});
|
||||
});
|
||||
|
||||
it(
|
||||
'warns on using differently ordered hooks ' +
|
||||
'(useImperativeHandleHelper, useMemoHelper) on subsequent renders',
|
||||
() => {
|
||||
function App(props) {
|
||||
/* eslint-disable no-unused-vars */
|
||||
if (props.update) {
|
||||
useMemoHelper();
|
||||
useImperativeHandleHelper();
|
||||
} else {
|
||||
useImperativeHandleHelper();
|
||||
useMemoHelper();
|
||||
}
|
||||
// This should not appear in the warning message because it occurs after the first mismatch
|
||||
useRefHelper();
|
||||
return null;
|
||||
/* eslint-enable no-unused-vars */
|
||||
}
|
||||
let root = ReactTestRenderer.create(<App update={false} />);
|
||||
expect(() => {
|
||||
try {
|
||||
root.update(<App update={true} />);
|
||||
} catch (error) {
|
||||
// Swapping certain types of hooks will cause runtime errors.
|
||||
// This is okay as far as this test is concerned.
|
||||
// We just want to verify that warnings are always logged.
|
||||
}
|
||||
}).toWarnDev([
|
||||
'Warning: React has detected a change in the order of Hooks called by App. ' +
|
||||
'This will lead to bugs and errors if not fixed. For more information, ' +
|
||||
'read the Rules of Hooks: https://fb.me/rules-of-hooks\n\n' +
|
||||
' Previous render Next render\n' +
|
||||
' ------------------------------------------------------\n' +
|
||||
`1. ${formatHookNamesToMatchErrorMessage(
|
||||
'ImperativeHandle',
|
||||
'Memo',
|
||||
)}\n` +
|
||||
' ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n' +
|
||||
' in App (at **)',
|
||||
]);
|
||||
|
||||
// further warnings for this component are silenced
|
||||
root.update(<App update={false} />);
|
||||
},
|
||||
);
|
||||
|
||||
it('detects a bad hook order even if the component throws', () => {
|
||||
const {useState, useReducer} = React;
|
||||
function useCustomHook() {
|
||||
useState(0);
|
||||
it('warns on using differently ordered hooks on subsequent renders', () => {
|
||||
const {useState, useReducer, useRef} = React;
|
||||
function useCustomHook() {
|
||||
return useState(0);
|
||||
}
|
||||
function App(props) {
|
||||
/* eslint-disable no-unused-vars */
|
||||
if (props.flip) {
|
||||
useCustomHook(0);
|
||||
useReducer((s, a) => a, 0);
|
||||
} else {
|
||||
useReducer((s, a) => a, 0);
|
||||
useCustomHook(0);
|
||||
}
|
||||
function App(props) {
|
||||
/* eslint-disable no-unused-vars */
|
||||
if (props.update) {
|
||||
useCustomHook();
|
||||
useReducer((s, a) => a, 0);
|
||||
throw new Error('custom error');
|
||||
} else {
|
||||
useReducer((s, a) => a, 0);
|
||||
useCustomHook();
|
||||
}
|
||||
return null;
|
||||
/* eslint-enable no-unused-vars */
|
||||
// This should not appear in the warning message because it occurs after
|
||||
// the first mismatch
|
||||
const ref = useRef(null);
|
||||
return null;
|
||||
/* eslint-enable no-unused-vars */
|
||||
}
|
||||
let root = ReactTestRenderer.create(<App flip={false} />);
|
||||
expect(() => {
|
||||
root.update(<App flip={true} />);
|
||||
}).toWarnDev([
|
||||
'Warning: React has detected a change in the order of Hooks called by App. ' +
|
||||
'This will lead to bugs and errors if not fixed. For more information, ' +
|
||||
'read the Rules of Hooks: https://fb.me/rules-of-hooks\n\n' +
|
||||
' Previous render Next render\n' +
|
||||
' -------------------------------\n' +
|
||||
'1. useReducer useState\n' +
|
||||
' ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n' +
|
||||
' in App (at **)',
|
||||
]);
|
||||
|
||||
// further warnings for this component are silenced
|
||||
root.update(<App flip={false} />);
|
||||
});
|
||||
|
||||
it('detects a bad hook order even if the component throws', () => {
|
||||
const {useState, useReducer} = React;
|
||||
function useCustomHook() {
|
||||
useState(0);
|
||||
}
|
||||
function App(props) {
|
||||
/* eslint-disable no-unused-vars */
|
||||
if (props.flip) {
|
||||
useCustomHook();
|
||||
useReducer((s, a) => a, 0);
|
||||
throw new Error('custom error');
|
||||
} else {
|
||||
useReducer((s, a) => a, 0);
|
||||
useCustomHook();
|
||||
}
|
||||
let root = ReactTestRenderer.create(<App update={false} />);
|
||||
expect(() => {
|
||||
expect(() => root.update(<App update={true} />)).toThrow(
|
||||
'custom error',
|
||||
);
|
||||
}).toWarnDev([
|
||||
'Warning: React has detected a change in the order of Hooks called by App. ' +
|
||||
'This will lead to bugs and errors if not fixed. For more information, ' +
|
||||
'read the Rules of Hooks: https://fb.me/rules-of-hooks\n\n' +
|
||||
' Previous render Next render\n' +
|
||||
' ------------------------------------------------------\n' +
|
||||
'1. useReducer useState\n' +
|
||||
' ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n',
|
||||
]);
|
||||
});
|
||||
return null;
|
||||
/* eslint-enable no-unused-vars */
|
||||
}
|
||||
let root = ReactTestRenderer.create(<App flip={false} />);
|
||||
expect(() => {
|
||||
expect(() => root.update(<App flip={true} />)).toThrow('custom error');
|
||||
}).toWarnDev([
|
||||
'Warning: React has detected a change in the order of Hooks called by App. ' +
|
||||
'This will lead to bugs and errors if not fixed. For more information, ' +
|
||||
'read the Rules of Hooks: https://fb.me/rules-of-hooks\n\n' +
|
||||
' Previous render Next render\n' +
|
||||
' -------------------------------\n' +
|
||||
'1. useReducer useState\n' +
|
||||
' ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^',
|
||||
]);
|
||||
});
|
||||
|
||||
// Regression test for #14674
|
||||
@@ -1628,97 +1366,4 @@ describe('ReactHooks', () => {
|
||||
),
|
||||
).toThrow('Hello');
|
||||
});
|
||||
|
||||
// Regression test for https://github.com/facebook/react/issues/14790
|
||||
it('does not fire a false positive warning when suspending memo', async () => {
|
||||
const {Suspense, useState} = React;
|
||||
|
||||
let wasSuspended = false;
|
||||
function trySuspend() {
|
||||
if (!wasSuspended) {
|
||||
throw new Promise(resolve => {
|
||||
wasSuspended = true;
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function Child() {
|
||||
useState();
|
||||
trySuspend();
|
||||
return 'hello';
|
||||
}
|
||||
|
||||
const Wrapper = React.memo(Child);
|
||||
const root = ReactTestRenderer.create(
|
||||
<Suspense fallback="loading">
|
||||
<Wrapper />
|
||||
</Suspense>,
|
||||
);
|
||||
expect(root).toMatchRenderedOutput('loading');
|
||||
await Promise.resolve();
|
||||
expect(root).toMatchRenderedOutput('hello');
|
||||
});
|
||||
|
||||
// Regression test for https://github.com/facebook/react/issues/14790
|
||||
it('does not fire a false positive warning when suspending forwardRef', async () => {
|
||||
const {Suspense, useState} = React;
|
||||
|
||||
let wasSuspended = false;
|
||||
function trySuspend() {
|
||||
if (!wasSuspended) {
|
||||
throw new Promise(resolve => {
|
||||
wasSuspended = true;
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function render(props, ref) {
|
||||
useState();
|
||||
trySuspend();
|
||||
return 'hello';
|
||||
}
|
||||
|
||||
const Wrapper = React.forwardRef(render);
|
||||
const root = ReactTestRenderer.create(
|
||||
<Suspense fallback="loading">
|
||||
<Wrapper />
|
||||
</Suspense>,
|
||||
);
|
||||
expect(root).toMatchRenderedOutput('loading');
|
||||
await Promise.resolve();
|
||||
expect(root).toMatchRenderedOutput('hello');
|
||||
});
|
||||
|
||||
// Regression test for https://github.com/facebook/react/issues/14790
|
||||
it('does not fire a false positive warning when suspending memo(forwardRef)', async () => {
|
||||
const {Suspense, useState} = React;
|
||||
|
||||
let wasSuspended = false;
|
||||
function trySuspend() {
|
||||
if (!wasSuspended) {
|
||||
throw new Promise(resolve => {
|
||||
wasSuspended = true;
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function render(props, ref) {
|
||||
useState();
|
||||
trySuspend();
|
||||
return 'hello';
|
||||
}
|
||||
|
||||
const Wrapper = React.memo(React.forwardRef(render));
|
||||
const root = ReactTestRenderer.create(
|
||||
<Suspense fallback="loading">
|
||||
<Wrapper />
|
||||
</Suspense>,
|
||||
);
|
||||
expect(root).toMatchRenderedOutput('loading');
|
||||
await Promise.resolve();
|
||||
expect(root).toMatchRenderedOutput('hello');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -454,9 +454,7 @@ describe('ReactHooksWithNoopRenderer', () => {
|
||||
|
||||
// Test that it works on update, too. This time the log is a bit different
|
||||
// because we started with reducerB instead of reducerA.
|
||||
ReactNoop.act(() => {
|
||||
counter.current.dispatch('reset');
|
||||
});
|
||||
counter.current.dispatch('reset');
|
||||
ReactNoop.render(<Counter ref={counter} />);
|
||||
expect(ReactNoop.flush()).toEqual([
|
||||
'Render: 0',
|
||||
@@ -1595,111 +1593,6 @@ describe('ReactHooksWithNoopRenderer', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('useImperativeHandle', () => {
|
||||
it('does not update when deps are the same', () => {
|
||||
const INCREMENT = 'INCREMENT';
|
||||
|
||||
function reducer(state, action) {
|
||||
return action === INCREMENT ? state + 1 : state;
|
||||
}
|
||||
|
||||
function Counter(props, ref) {
|
||||
const [count, dispatch] = useReducer(reducer, 0);
|
||||
useImperativeHandle(ref, () => ({count, dispatch}), []);
|
||||
return <Text text={'Count: ' + count} />;
|
||||
}
|
||||
|
||||
Counter = forwardRef(Counter);
|
||||
const counter = React.createRef(null);
|
||||
ReactNoop.render(<Counter ref={counter} />);
|
||||
ReactNoop.flush();
|
||||
expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]);
|
||||
expect(counter.current.count).toBe(0);
|
||||
|
||||
act(() => {
|
||||
counter.current.dispatch(INCREMENT);
|
||||
});
|
||||
ReactNoop.flush();
|
||||
expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]);
|
||||
// Intentionally not updated because of [] deps:
|
||||
expect(counter.current.count).toBe(0);
|
||||
});
|
||||
|
||||
// Regression test for https://github.com/facebook/react/issues/14782
|
||||
it('automatically updates when deps are not specified', () => {
|
||||
const INCREMENT = 'INCREMENT';
|
||||
|
||||
function reducer(state, action) {
|
||||
return action === INCREMENT ? state + 1 : state;
|
||||
}
|
||||
|
||||
function Counter(props, ref) {
|
||||
const [count, dispatch] = useReducer(reducer, 0);
|
||||
useImperativeHandle(ref, () => ({count, dispatch}));
|
||||
return <Text text={'Count: ' + count} />;
|
||||
}
|
||||
|
||||
Counter = forwardRef(Counter);
|
||||
const counter = React.createRef(null);
|
||||
ReactNoop.render(<Counter ref={counter} />);
|
||||
ReactNoop.flush();
|
||||
expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]);
|
||||
expect(counter.current.count).toBe(0);
|
||||
|
||||
act(() => {
|
||||
counter.current.dispatch(INCREMENT);
|
||||
});
|
||||
ReactNoop.flush();
|
||||
expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]);
|
||||
expect(counter.current.count).toBe(1);
|
||||
});
|
||||
|
||||
it('updates when deps are different', () => {
|
||||
const INCREMENT = 'INCREMENT';
|
||||
|
||||
function reducer(state, action) {
|
||||
return action === INCREMENT ? state + 1 : state;
|
||||
}
|
||||
|
||||
let totalRefUpdates = 0;
|
||||
function Counter(props, ref) {
|
||||
const [count, dispatch] = useReducer(reducer, 0);
|
||||
useImperativeHandle(
|
||||
ref,
|
||||
() => {
|
||||
totalRefUpdates++;
|
||||
return {count, dispatch};
|
||||
},
|
||||
[count],
|
||||
);
|
||||
return <Text text={'Count: ' + count} />;
|
||||
}
|
||||
|
||||
Counter = forwardRef(Counter);
|
||||
const counter = React.createRef(null);
|
||||
ReactNoop.render(<Counter ref={counter} />);
|
||||
ReactNoop.flush();
|
||||
expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]);
|
||||
expect(counter.current.count).toBe(0);
|
||||
expect(totalRefUpdates).toBe(1);
|
||||
|
||||
act(() => {
|
||||
counter.current.dispatch(INCREMENT);
|
||||
});
|
||||
ReactNoop.flush();
|
||||
expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]);
|
||||
expect(counter.current.count).toBe(1);
|
||||
expect(totalRefUpdates).toBe(2);
|
||||
|
||||
// Update that doesn't change the ref dependencies
|
||||
ReactNoop.render(<Counter ref={counter} />);
|
||||
ReactNoop.flush();
|
||||
expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]);
|
||||
expect(counter.current.count).toBe(1);
|
||||
expect(totalRefUpdates).toBe(2); // Should not increase since last time
|
||||
});
|
||||
});
|
||||
|
||||
describe('progressive enhancement (not supported)', () => {
|
||||
it('mount additional state', () => {
|
||||
let updateA;
|
||||
@@ -1740,20 +1633,8 @@ describe('ReactHooksWithNoopRenderer', () => {
|
||||
|
||||
ReactNoop.render(<App loadC={true} />);
|
||||
expect(() => {
|
||||
expect(() => {
|
||||
expect(ReactNoop.flush()).toEqual(['A: 2, B: 3, C: 0']);
|
||||
}).toThrow('Rendered more hooks than during the previous render');
|
||||
}).toWarnDev([
|
||||
'Warning: React has detected a change in the order of Hooks called by App. ' +
|
||||
'This will lead to bugs and errors if not fixed. For more information, ' +
|
||||
'read the Rules of Hooks: https://fb.me/rules-of-hooks\n\n' +
|
||||
' Previous render Next render\n' +
|
||||
' ------------------------------------------------------\n' +
|
||||
'1. useState useState\n' +
|
||||
'2. useState useState\n' +
|
||||
'3. undefined useState\n' +
|
||||
' ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n',
|
||||
]);
|
||||
expect(ReactNoop.flush()).toEqual(['A: 2, B: 3, C: 0']);
|
||||
}).toThrow('Rendered more hooks than during the previous render');
|
||||
|
||||
// Uncomment if/when we support this again
|
||||
// expect(ReactNoop.getChildren()).toEqual([span('A: 2, B: 3, C: 0')]);
|
||||
@@ -1831,19 +1712,8 @@ describe('ReactHooksWithNoopRenderer', () => {
|
||||
|
||||
ReactNoop.render(<App showMore={true} />);
|
||||
expect(() => {
|
||||
expect(() => {
|
||||
expect(ReactNoop.flush()).toEqual([]);
|
||||
}).toThrow('Rendered more hooks than during the previous render');
|
||||
}).toWarnDev([
|
||||
'Warning: React has detected a change in the order of Hooks called by App. ' +
|
||||
'This will lead to bugs and errors if not fixed. For more information, ' +
|
||||
'read the Rules of Hooks: https://fb.me/rules-of-hooks\n\n' +
|
||||
' Previous render Next render\n' +
|
||||
' ------------------------------------------------------\n' +
|
||||
'1. useEffect useEffect\n' +
|
||||
'2. undefined useEffect\n' +
|
||||
' ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n',
|
||||
]);
|
||||
expect(ReactNoop.flush()).toEqual([]);
|
||||
}).toThrow('Rendered more hooks than during the previous render');
|
||||
|
||||
// Uncomment if/when we support this again
|
||||
// ReactNoop.flushPassiveEffects();
|
||||
|
||||
@@ -15,7 +15,7 @@ import invariant from 'shared/invariant';
|
||||
const ReactFiberErrorDialogWWW = require('ReactFiberErrorDialog');
|
||||
invariant(
|
||||
typeof ReactFiberErrorDialogWWW.showErrorDialog === 'function',
|
||||
'Expected ReactFiberErrorDialog.showErrorDialog to be a function.',
|
||||
'Expected ReactFiberErrorDialog.showErrorDialog to existbe a function.',
|
||||
);
|
||||
|
||||
export function showErrorDialog(capturedError: CapturedError): boolean {
|
||||
|
||||
@@ -29,7 +29,6 @@ export opaque type Props = mixed; // eslint-disable-line no-undef
|
||||
export opaque type Container = mixed; // eslint-disable-line no-undef
|
||||
export opaque type Instance = mixed; // eslint-disable-line no-undef
|
||||
export opaque type TextInstance = mixed; // eslint-disable-line no-undef
|
||||
export opaque type SuspenseInstance = mixed; // eslint-disable-line no-undef
|
||||
export opaque type HydratableInstance = mixed; // eslint-disable-line no-undef
|
||||
export opaque type PublicInstance = mixed; // eslint-disable-line no-undef
|
||||
export opaque type HostContext = mixed; // eslint-disable-line no-undef
|
||||
@@ -105,17 +104,10 @@ export const createHiddenTextInstance = $$$hostConfig.createHiddenTextInstance;
|
||||
// -------------------
|
||||
export const canHydrateInstance = $$$hostConfig.canHydrateInstance;
|
||||
export const canHydrateTextInstance = $$$hostConfig.canHydrateTextInstance;
|
||||
export const canHydrateSuspenseInstance =
|
||||
$$$hostConfig.canHydrateSuspenseInstance;
|
||||
export const getNextHydratableSibling = $$$hostConfig.getNextHydratableSibling;
|
||||
export const getFirstHydratableChild = $$$hostConfig.getFirstHydratableChild;
|
||||
export const hydrateInstance = $$$hostConfig.hydrateInstance;
|
||||
export const hydrateTextInstance = $$$hostConfig.hydrateTextInstance;
|
||||
export const getNextHydratableInstanceAfterSuspenseInstance =
|
||||
$$$hostConfig.getNextHydratableInstanceAfterSuspenseInstance;
|
||||
export const clearSuspenseBoundary = $$$hostConfig.clearSuspenseBoundary;
|
||||
export const clearSuspenseBoundaryFromContainer =
|
||||
$$$hostConfig.clearSuspenseBoundaryFromContainer;
|
||||
export const didNotMatchHydratedContainerTextInstance =
|
||||
$$$hostConfig.didNotMatchHydratedContainerTextInstance;
|
||||
export const didNotMatchHydratedTextInstance =
|
||||
@@ -127,11 +119,7 @@ export const didNotFindHydratableContainerInstance =
|
||||
$$$hostConfig.didNotFindHydratableContainerInstance;
|
||||
export const didNotFindHydratableContainerTextInstance =
|
||||
$$$hostConfig.didNotFindHydratableContainerTextInstance;
|
||||
export const didNotFindHydratableContainerSuspenseInstance =
|
||||
$$$hostConfig.didNotFindHydratableContainerSuspenseInstance;
|
||||
export const didNotFindHydratableInstance =
|
||||
$$$hostConfig.didNotFindHydratableInstance;
|
||||
export const didNotFindHydratableTextInstance =
|
||||
$$$hostConfig.didNotFindHydratableTextInstance;
|
||||
export const didNotFindHydratableSuspenseInstance =
|
||||
$$$hostConfig.didNotFindHydratableSuspenseInstance;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "react-test-renderer",
|
||||
"version": "16.8.4",
|
||||
"version": "16.8.0",
|
||||
"description": "React package for snapshot testing.",
|
||||
"main": "index.js",
|
||||
"repository": {
|
||||
@@ -21,8 +21,8 @@
|
||||
"dependencies": {
|
||||
"object-assign": "^4.1.1",
|
||||
"prop-types": "^15.6.2",
|
||||
"react-is": "^16.8.4",
|
||||
"scheduler": "^0.13.4"
|
||||
"react-is": "^16.8.0",
|
||||
"scheduler": "^0.13.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.0.0"
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"keywords": [
|
||||
"react"
|
||||
],
|
||||
"version": "16.8.4",
|
||||
"version": "16.8.0",
|
||||
"homepage": "https://reactjs.org/",
|
||||
"bugs": "https://github.com/facebook/react/issues",
|
||||
"license": "MIT",
|
||||
@@ -29,7 +29,7 @@
|
||||
"loose-envify": "^1.1.0",
|
||||
"object-assign": "^4.1.1",
|
||||
"prop-types": "^15.6.2",
|
||||
"scheduler": "^0.13.4"
|
||||
"scheduler": "^0.13.0"
|
||||
},
|
||||
"browserify": {
|
||||
"transform": [
|
||||
|
||||
@@ -12,17 +12,11 @@ import {
|
||||
unstable_now,
|
||||
unstable_scheduleCallback,
|
||||
unstable_runWithPriority,
|
||||
unstable_next,
|
||||
unstable_getFirstCallbackNode,
|
||||
unstable_pauseExecution,
|
||||
unstable_continueExecution,
|
||||
unstable_wrapCallback,
|
||||
unstable_getCurrentPriorityLevel,
|
||||
unstable_IdlePriority,
|
||||
unstable_ImmediatePriority,
|
||||
unstable_LowPriority,
|
||||
unstable_NormalPriority,
|
||||
unstable_UserBlockingPriority,
|
||||
} from 'scheduler';
|
||||
import {
|
||||
__interactionsRef,
|
||||
@@ -59,17 +53,11 @@ if (__UMD__) {
|
||||
unstable_now,
|
||||
unstable_scheduleCallback,
|
||||
unstable_runWithPriority,
|
||||
unstable_next,
|
||||
unstable_wrapCallback,
|
||||
unstable_getFirstCallbackNode,
|
||||
unstable_pauseExecution,
|
||||
unstable_continueExecution,
|
||||
unstable_getCurrentPriorityLevel,
|
||||
unstable_IdlePriority,
|
||||
unstable_ImmediatePriority,
|
||||
unstable_LowPriority,
|
||||
unstable_NormalPriority,
|
||||
unstable_UserBlockingPriority,
|
||||
},
|
||||
SchedulerTracing: {
|
||||
__interactionsRef,
|
||||
|
||||
@@ -54,13 +54,6 @@
|
||||
);
|
||||
}
|
||||
|
||||
function unstable_next() {
|
||||
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Scheduler.unstable_next.apply(
|
||||
this,
|
||||
arguments
|
||||
);
|
||||
}
|
||||
|
||||
function unstable_wrapCallback() {
|
||||
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Scheduler.unstable_wrapCallback.apply(
|
||||
this,
|
||||
@@ -102,31 +95,10 @@
|
||||
unstable_cancelCallback: unstable_cancelCallback,
|
||||
unstable_shouldYield: unstable_shouldYield,
|
||||
unstable_runWithPriority: unstable_runWithPriority,
|
||||
unstable_next: unstable_next,
|
||||
unstable_wrapCallback: unstable_wrapCallback,
|
||||
unstable_getCurrentPriorityLevel: unstable_getCurrentPriorityLevel,
|
||||
unstable_continueExecution: unstable_continueExecution,
|
||||
unstable_pauseExecution: unstable_pauseExecution,
|
||||
unstable_getFirstCallbackNode: unstable_getFirstCallbackNode,
|
||||
get unstable_IdlePriority() {
|
||||
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
|
||||
.Scheduler.unstable_IdlePriority;
|
||||
},
|
||||
get unstable_ImmediatePriority() {
|
||||
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
|
||||
.Scheduler.unstable_ImmediatePriority;
|
||||
},
|
||||
get unstable_LowPriority() {
|
||||
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
|
||||
.Scheduler.unstable_LowPriority;
|
||||
},
|
||||
get unstable_NormalPriority() {
|
||||
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
|
||||
.Scheduler.unstable_NormalPriority;
|
||||
},
|
||||
get unstable_UserBlockingPriority() {
|
||||
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
|
||||
.Scheduler.unstable_UserBlockingPriority;
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -54,13 +54,6 @@
|
||||
);
|
||||
}
|
||||
|
||||
function unstable_next() {
|
||||
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Scheduler.unstable_next.apply(
|
||||
this,
|
||||
arguments
|
||||
);
|
||||
}
|
||||
|
||||
function unstable_wrapCallback() {
|
||||
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Scheduler.unstable_wrapCallback.apply(
|
||||
this,
|
||||
@@ -96,31 +89,10 @@
|
||||
unstable_cancelCallback: unstable_cancelCallback,
|
||||
unstable_shouldYield: unstable_shouldYield,
|
||||
unstable_runWithPriority: unstable_runWithPriority,
|
||||
unstable_next: unstable_next,
|
||||
unstable_wrapCallback: unstable_wrapCallback,
|
||||
unstable_getCurrentPriorityLevel: unstable_getCurrentPriorityLevel,
|
||||
unstable_continueExecution: unstable_continueExecution,
|
||||
unstable_pauseExecution: unstable_pauseExecution,
|
||||
unstable_getFirstCallbackNode: unstable_getFirstCallbackNode,
|
||||
get unstable_IdlePriority() {
|
||||
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
|
||||
.Scheduler.unstable_IdlePriority;
|
||||
},
|
||||
get unstable_ImmediatePriority() {
|
||||
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
|
||||
.Scheduler.unstable_ImmediatePriority;
|
||||
},
|
||||
get unstable_LowPriority() {
|
||||
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
|
||||
.Scheduler.unstable_LowPriority;
|
||||
},
|
||||
get unstable_NormalPriority() {
|
||||
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
|
||||
.Scheduler.unstable_NormalPriority;
|
||||
},
|
||||
get unstable_UserBlockingPriority() {
|
||||
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
|
||||
.Scheduler.unstable_UserBlockingPriority;
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -54,13 +54,6 @@
|
||||
);
|
||||
}
|
||||
|
||||
function unstable_next() {
|
||||
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Scheduler.unstable_next.apply(
|
||||
this,
|
||||
arguments
|
||||
);
|
||||
}
|
||||
|
||||
function unstable_wrapCallback() {
|
||||
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Scheduler.unstable_wrapCallback.apply(
|
||||
this,
|
||||
@@ -96,31 +89,10 @@
|
||||
unstable_cancelCallback: unstable_cancelCallback,
|
||||
unstable_shouldYield: unstable_shouldYield,
|
||||
unstable_runWithPriority: unstable_runWithPriority,
|
||||
unstable_next: unstable_next,
|
||||
unstable_wrapCallback: unstable_wrapCallback,
|
||||
unstable_getCurrentPriorityLevel: unstable_getCurrentPriorityLevel,
|
||||
unstable_continueExecution: unstable_continueExecution,
|
||||
unstable_pauseExecution: unstable_pauseExecution,
|
||||
unstable_getFirstCallbackNode: unstable_getFirstCallbackNode,
|
||||
get unstable_IdlePriority() {
|
||||
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
|
||||
.Scheduler.unstable_IdlePriority;
|
||||
},
|
||||
get unstable_ImmediatePriority() {
|
||||
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
|
||||
.Scheduler.unstable_ImmediatePriority;
|
||||
},
|
||||
get unstable_LowPriority() {
|
||||
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
|
||||
.Scheduler.unstable_LowPriority;
|
||||
},
|
||||
get unstable_NormalPriority() {
|
||||
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
|
||||
.Scheduler.unstable_NormalPriority;
|
||||
},
|
||||
get unstable_UserBlockingPriority() {
|
||||
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
|
||||
.Scheduler.unstable_UserBlockingPriority;
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "scheduler",
|
||||
"version": "0.13.4",
|
||||
"version": "0.13.0",
|
||||
"description": "Cooperative scheduler for the browser environment.",
|
||||
"main": "index.js",
|
||||
"repository": {
|
||||
|
||||
@@ -264,37 +264,6 @@ function unstable_runWithPriority(priorityLevel, eventHandler) {
|
||||
}
|
||||
}
|
||||
|
||||
function unstable_next(eventHandler) {
|
||||
let priorityLevel;
|
||||
switch (currentPriorityLevel) {
|
||||
case ImmediatePriority:
|
||||
case UserBlockingPriority:
|
||||
case NormalPriority:
|
||||
// Shift down to normal priority
|
||||
priorityLevel = NormalPriority;
|
||||
break;
|
||||
default:
|
||||
// Anything lower than normal priority should remain at the current level.
|
||||
priorityLevel = currentPriorityLevel;
|
||||
break;
|
||||
}
|
||||
|
||||
var previousPriorityLevel = currentPriorityLevel;
|
||||
var previousEventStartTime = currentEventStartTime;
|
||||
currentPriorityLevel = priorityLevel;
|
||||
currentEventStartTime = getCurrentTime();
|
||||
|
||||
try {
|
||||
return eventHandler();
|
||||
} finally {
|
||||
currentPriorityLevel = previousPriorityLevel;
|
||||
currentEventStartTime = previousEventStartTime;
|
||||
|
||||
// Before exiting, flush all the immediate work that was scheduled.
|
||||
flushImmediateWork();
|
||||
}
|
||||
}
|
||||
|
||||
function unstable_wrapCallback(callback) {
|
||||
var parentPriorityLevel = currentPriorityLevel;
|
||||
return function() {
|
||||
@@ -719,7 +688,6 @@ export {
|
||||
IdlePriority as unstable_IdlePriority,
|
||||
LowPriority as unstable_LowPriority,
|
||||
unstable_runWithPriority,
|
||||
unstable_next,
|
||||
unstable_scheduleCallback,
|
||||
unstable_cancelCallback,
|
||||
unstable_wrapCallback,
|
||||
|
||||
@@ -140,7 +140,7 @@ describe('SchedulerDOM', () => {
|
||||
expect(callbackLog).toEqual(['A', 'B']);
|
||||
});
|
||||
|
||||
it("accepts callbacks between animationFrame and postMessage and doesn't stall", () => {
|
||||
it("accepts callbacks betweeen animationFrame and postMessage and doesn't stall", () => {
|
||||
const {unstable_scheduleCallback: scheduleCallback} = Scheduler;
|
||||
const callbackLog = [];
|
||||
const callbackA = jest.fn(() => callbackLog.push('A'));
|
||||
|
||||
@@ -17,17 +17,8 @@ describe('Scheduling UMD bundle', () => {
|
||||
});
|
||||
|
||||
function filterPrivateKeys(name) {
|
||||
// Be very careful adding things to this whitelist!
|
||||
// It's easy to introduce bugs by doing it:
|
||||
// https://github.com/facebook/react/issues/14904
|
||||
switch (name) {
|
||||
case '__interactionsRef':
|
||||
case '__subscriberRef':
|
||||
// Don't forward these. (TODO: why?)
|
||||
return false;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
// TODO: Figure out how to forward priority levels.
|
||||
return !name.startsWith('_') && !name.endsWith('Priority');
|
||||
}
|
||||
|
||||
function validateForwardedAPIs(api, forwardedAPIs) {
|
||||
|
||||
@@ -22,25 +22,18 @@ function shim(...args: any) {
|
||||
}
|
||||
|
||||
// Hydration (when unsupported)
|
||||
export type SuspenseInstance = mixed;
|
||||
export const supportsHydration = false;
|
||||
export const canHydrateInstance = shim;
|
||||
export const canHydrateTextInstance = shim;
|
||||
export const canHydrateSuspenseInstance = shim;
|
||||
export const getNextHydratableSibling = shim;
|
||||
export const getFirstHydratableChild = shim;
|
||||
export const hydrateInstance = shim;
|
||||
export const hydrateTextInstance = shim;
|
||||
export const getNextHydratableInstanceAfterSuspenseInstance = shim;
|
||||
export const clearSuspenseBoundary = shim;
|
||||
export const clearSuspenseBoundaryFromContainer = shim;
|
||||
export const didNotMatchHydratedContainerTextInstance = shim;
|
||||
export const didNotMatchHydratedTextInstance = shim;
|
||||
export const didNotHydrateContainerInstance = shim;
|
||||
export const didNotHydrateInstance = shim;
|
||||
export const didNotFindHydratableContainerInstance = shim;
|
||||
export const didNotFindHydratableContainerTextInstance = shim;
|
||||
export const didNotFindHydratableContainerSuspenseInstance = shim;
|
||||
export const didNotFindHydratableInstance = shim;
|
||||
export const didNotFindHydratableTextInstance = shim;
|
||||
export const didNotFindHydratableSuspenseInstance = shim;
|
||||
|
||||
@@ -8,4 +8,4 @@
|
||||
'use strict';
|
||||
|
||||
// TODO: this is special because it gets imported during build.
|
||||
module.exports = '16.8.4';
|
||||
module.exports = '16.8.0';
|
||||
|
||||
@@ -46,4 +46,3 @@ export const MemoComponent = 14;
|
||||
export const SimpleMemoComponent = 15;
|
||||
export const LazyComponent = 16;
|
||||
export const IncompleteClassComponent = 17;
|
||||
export const DehydratedSuspenseComponent = 18;
|
||||
|
||||
@@ -14,6 +14,7 @@ import typeof * as FeatureFlagsShimType from './ReactFeatureFlags.www';
|
||||
export const {
|
||||
debugRenderPhaseSideEffects,
|
||||
debugRenderPhaseSideEffectsForStrictMode,
|
||||
enableSuspenseServerRenderer,
|
||||
replayFailedUnitOfWorkWithInvokeGuardedCallback,
|
||||
warnAboutDeprecatedLifecycles,
|
||||
disableInputAttributeSyncing,
|
||||
@@ -34,8 +35,6 @@ export const enableSchedulerDebugging = true;
|
||||
|
||||
export const enableStableConcurrentModeAPIs = false;
|
||||
|
||||
export const enableSuspenseServerRenderer = true;
|
||||
|
||||
let refCount = 0;
|
||||
export function addUserTimingListener() {
|
||||
if (__DEV__) {
|
||||
|
||||
@@ -17,16 +17,8 @@ const {
|
||||
unstable_scheduleCallback,
|
||||
unstable_shouldYield,
|
||||
unstable_getFirstCallbackNode,
|
||||
unstable_runWithPriority,
|
||||
unstable_next,
|
||||
unstable_continueExecution,
|
||||
unstable_pauseExecution,
|
||||
unstable_getCurrentPriorityLevel,
|
||||
unstable_ImmediatePriority,
|
||||
unstable_UserBlockingPriority,
|
||||
unstable_NormalPriority,
|
||||
unstable_LowPriority,
|
||||
unstable_IdlePriority,
|
||||
} = ReactInternals.Scheduler;
|
||||
|
||||
export {
|
||||
@@ -35,14 +27,6 @@ export {
|
||||
unstable_scheduleCallback,
|
||||
unstable_shouldYield,
|
||||
unstable_getFirstCallbackNode,
|
||||
unstable_runWithPriority,
|
||||
unstable_next,
|
||||
unstable_continueExecution,
|
||||
unstable_pauseExecution,
|
||||
unstable_getCurrentPriorityLevel,
|
||||
unstable_ImmediatePriority,
|
||||
unstable_UserBlockingPriority,
|
||||
unstable_NormalPriority,
|
||||
unstable_LowPriority,
|
||||
unstable_IdlePriority,
|
||||
};
|
||||
|
||||
@@ -36,7 +36,7 @@ if (__DEV__) {
|
||||
// invokeGuardedCallback uses a try-catch, all user exceptions are treated
|
||||
// like caught exceptions, and the DevTools won't pause unless the developer
|
||||
// takes the extra step of enabling pause on caught exceptions. This is
|
||||
// unintuitive, though, because even though React has caught the error, from
|
||||
// untintuitive, though, because even though React has caught the error, from
|
||||
// the developer's perspective, the error is uncaught.
|
||||
//
|
||||
// To preserve the expected "Pause on exceptions" behavior, we don't use a
|
||||
|
||||
@@ -311,13 +311,5 @@
|
||||
"309": "Function components cannot have refs. Did you mean to use React.forwardRef()?",
|
||||
"310": "Rendered more hooks than during the previous render.",
|
||||
"311": "Should have a queue. This is likely a bug in React. Please file an issue.",
|
||||
"312": "Rendered more hooks than during the previous render",
|
||||
"313": "Unknown priority level. This error is likely caused by a bug in React. Please file an issue.",
|
||||
"314": "Pinged unknown suspense boundary type. This is probably a bug in React.",
|
||||
"315": "Suspense boundaries are never on the root. This is probably a bug in React.",
|
||||
"316": "Expected skipPastDehydratedSuspenseInstance() to never be called. This error is likely caused by a bug in React. Please file an issue.",
|
||||
"317": "Expected to have a hydrated suspense instance. This error is likely caused by a bug in React. Please file an issue.",
|
||||
"318": "A dehydrated suspense component was completed without a hydrated node. This is probably a bug in React.",
|
||||
"319": "A dehydrated suspense boundary must commit before trying to render. This is probably a bug in React.",
|
||||
"320": "Expected ReactFiberErrorDialog.showErrorDialog to be a function."
|
||||
"312": "Rendered more hooks than during the previous render"
|
||||
}
|
||||
|
||||
@@ -21,9 +21,7 @@ const run = async ({cwd, packages, tags}) => {
|
||||
clear();
|
||||
|
||||
if (tags.length === 1 && tags[0] === 'canary') {
|
||||
console.log(
|
||||
theme`{header A canary release} {version ${version}} {header has been published!}`
|
||||
);
|
||||
console.log(theme.header`A canary release has been pulbished!`);
|
||||
} else {
|
||||
console.log(
|
||||
theme.caution`The release has been published but you're not done yet!`
|
||||
|
||||
@@ -4,29 +4,29 @@
|
||||
"filename": "react.development.js",
|
||||
"bundleType": "UMD_DEV",
|
||||
"packageName": "react",
|
||||
"size": 101989,
|
||||
"gzip": 26428
|
||||
"size": 97676,
|
||||
"gzip": 25688
|
||||
},
|
||||
{
|
||||
"filename": "react.production.min.js",
|
||||
"bundleType": "UMD_PROD",
|
||||
"packageName": "react",
|
||||
"size": 12548,
|
||||
"gzip": 4823
|
||||
"size": 11771,
|
||||
"gzip": 4678
|
||||
},
|
||||
{
|
||||
"filename": "react.development.js",
|
||||
"bundleType": "NODE_DEV",
|
||||
"packageName": "react",
|
||||
"size": 63522,
|
||||
"gzip": 17094
|
||||
"size": 60944,
|
||||
"gzip": 16507
|
||||
},
|
||||
{
|
||||
"filename": "react.production.min.js",
|
||||
"bundleType": "NODE_PROD",
|
||||
"packageName": "react",
|
||||
"size": 6831,
|
||||
"gzip": 2817
|
||||
"size": 6223,
|
||||
"gzip": 2655
|
||||
},
|
||||
{
|
||||
"filename": "React-dev.js",
|
||||
@@ -46,29 +46,29 @@
|
||||
"filename": "react-dom.development.js",
|
||||
"bundleType": "UMD_DEV",
|
||||
"packageName": "react-dom",
|
||||
"size": 783692,
|
||||
"gzip": 178609
|
||||
"size": 725813,
|
||||
"gzip": 167799
|
||||
},
|
||||
{
|
||||
"filename": "react-dom.production.min.js",
|
||||
"bundleType": "UMD_PROD",
|
||||
"packageName": "react-dom",
|
||||
"size": 107842,
|
||||
"gzip": 34729
|
||||
"size": 99920,
|
||||
"gzip": 32521
|
||||
},
|
||||
{
|
||||
"filename": "react-dom.development.js",
|
||||
"bundleType": "NODE_DEV",
|
||||
"packageName": "react-dom",
|
||||
"size": 778167,
|
||||
"gzip": 177083
|
||||
"size": 721011,
|
||||
"gzip": 166399
|
||||
},
|
||||
{
|
||||
"filename": "react-dom.production.min.js",
|
||||
"bundleType": "NODE_PROD",
|
||||
"packageName": "react-dom",
|
||||
"size": 108009,
|
||||
"gzip": 34209
|
||||
"size": 99914,
|
||||
"gzip": 32052
|
||||
},
|
||||
{
|
||||
"filename": "ReactDOM-dev.js",
|
||||
@@ -88,29 +88,29 @@
|
||||
"filename": "react-dom-test-utils.development.js",
|
||||
"bundleType": "UMD_DEV",
|
||||
"packageName": "react-dom",
|
||||
"size": 48181,
|
||||
"gzip": 13291
|
||||
"size": 45937,
|
||||
"gzip": 12585
|
||||
},
|
||||
{
|
||||
"filename": "react-dom-test-utils.production.min.js",
|
||||
"bundleType": "UMD_PROD",
|
||||
"packageName": "react-dom",
|
||||
"size": 10504,
|
||||
"gzip": 3882
|
||||
"size": 10199,
|
||||
"gzip": 3787
|
||||
},
|
||||
{
|
||||
"filename": "react-dom-test-utils.development.js",
|
||||
"bundleType": "NODE_DEV",
|
||||
"packageName": "react-dom",
|
||||
"size": 47895,
|
||||
"gzip": 13220
|
||||
"size": 45651,
|
||||
"gzip": 12521
|
||||
},
|
||||
{
|
||||
"filename": "react-dom-test-utils.production.min.js",
|
||||
"bundleType": "NODE_PROD",
|
||||
"packageName": "react-dom",
|
||||
"size": 10281,
|
||||
"gzip": 3814
|
||||
"size": 9969,
|
||||
"gzip": 3726
|
||||
},
|
||||
{
|
||||
"filename": "ReactTestUtils-dev.js",
|
||||
@@ -123,7 +123,7 @@
|
||||
"filename": "react-dom-unstable-native-dependencies.development.js",
|
||||
"bundleType": "UMD_DEV",
|
||||
"packageName": "react-dom",
|
||||
"size": 62058,
|
||||
"size": 62059,
|
||||
"gzip": 16289
|
||||
},
|
||||
{
|
||||
@@ -137,7 +137,7 @@
|
||||
"filename": "react-dom-unstable-native-dependencies.development.js",
|
||||
"bundleType": "NODE_DEV",
|
||||
"packageName": "react-dom",
|
||||
"size": 61722,
|
||||
"size": 61723,
|
||||
"gzip": 16156
|
||||
},
|
||||
{
|
||||
@@ -165,29 +165,29 @@
|
||||
"filename": "react-dom-server.browser.development.js",
|
||||
"bundleType": "UMD_DEV",
|
||||
"packageName": "react-dom",
|
||||
"size": 129798,
|
||||
"gzip": 34602
|
||||
"size": 122056,
|
||||
"gzip": 32469
|
||||
},
|
||||
{
|
||||
"filename": "react-dom-server.browser.production.min.js",
|
||||
"bundleType": "UMD_PROD",
|
||||
"packageName": "react-dom",
|
||||
"size": 19117,
|
||||
"gzip": 7343
|
||||
"size": 16402,
|
||||
"gzip": 6237
|
||||
},
|
||||
{
|
||||
"filename": "react-dom-server.browser.development.js",
|
||||
"bundleType": "NODE_DEV",
|
||||
"packageName": "react-dom",
|
||||
"size": 125836,
|
||||
"gzip": 33642
|
||||
"size": 118094,
|
||||
"gzip": 31495
|
||||
},
|
||||
{
|
||||
"filename": "react-dom-server.browser.production.min.js",
|
||||
"bundleType": "NODE_PROD",
|
||||
"packageName": "react-dom",
|
||||
"size": 19037,
|
||||
"gzip": 7325
|
||||
"size": 16301,
|
||||
"gzip": 6233
|
||||
},
|
||||
{
|
||||
"filename": "ReactDOMServer-dev.js",
|
||||
@@ -207,43 +207,43 @@
|
||||
"filename": "react-dom-server.node.development.js",
|
||||
"bundleType": "NODE_DEV",
|
||||
"packageName": "react-dom",
|
||||
"size": 127943,
|
||||
"gzip": 34197
|
||||
"size": 120062,
|
||||
"gzip": 32036
|
||||
},
|
||||
{
|
||||
"filename": "react-dom-server.node.production.min.js",
|
||||
"bundleType": "NODE_PROD",
|
||||
"packageName": "react-dom",
|
||||
"size": 19930,
|
||||
"gzip": 7641
|
||||
"size": 17126,
|
||||
"gzip": 6544
|
||||
},
|
||||
{
|
||||
"filename": "react-art.development.js",
|
||||
"bundleType": "UMD_DEV",
|
||||
"packageName": "react-art",
|
||||
"size": 554932,
|
||||
"gzip": 120585
|
||||
"size": 507846,
|
||||
"gzip": 112120
|
||||
},
|
||||
{
|
||||
"filename": "react-art.production.min.js",
|
||||
"bundleType": "UMD_PROD",
|
||||
"packageName": "react-art",
|
||||
"size": 99655,
|
||||
"gzip": 30575
|
||||
"size": 91897,
|
||||
"gzip": 28219
|
||||
},
|
||||
{
|
||||
"filename": "react-art.development.js",
|
||||
"bundleType": "NODE_DEV",
|
||||
"packageName": "react-art",
|
||||
"size": 484327,
|
||||
"gzip": 102945
|
||||
"size": 437983,
|
||||
"gzip": 94680
|
||||
},
|
||||
{
|
||||
"filename": "react-art.production.min.js",
|
||||
"bundleType": "NODE_PROD",
|
||||
"packageName": "react-art",
|
||||
"size": 63856,
|
||||
"gzip": 19480
|
||||
"size": 56044,
|
||||
"gzip": 17298
|
||||
},
|
||||
{
|
||||
"filename": "ReactART-dev.js",
|
||||
@@ -291,29 +291,29 @@
|
||||
"filename": "react-test-renderer.development.js",
|
||||
"bundleType": "UMD_DEV",
|
||||
"packageName": "react-test-renderer",
|
||||
"size": 496691,
|
||||
"gzip": 105367
|
||||
"size": 450951,
|
||||
"gzip": 97435
|
||||
},
|
||||
{
|
||||
"filename": "react-test-renderer.production.min.js",
|
||||
"bundleType": "UMD_PROD",
|
||||
"packageName": "react-test-renderer",
|
||||
"size": 65252,
|
||||
"gzip": 19980
|
||||
"size": 57293,
|
||||
"gzip": 17617
|
||||
},
|
||||
{
|
||||
"filename": "react-test-renderer.development.js",
|
||||
"bundleType": "NODE_DEV",
|
||||
"packageName": "react-test-renderer",
|
||||
"size": 490997,
|
||||
"gzip": 104031
|
||||
"size": 446052,
|
||||
"gzip": 96263
|
||||
},
|
||||
{
|
||||
"filename": "react-test-renderer.production.min.js",
|
||||
"bundleType": "NODE_PROD",
|
||||
"packageName": "react-test-renderer",
|
||||
"size": 64908,
|
||||
"gzip": 19647
|
||||
"size": 56955,
|
||||
"gzip": 17453
|
||||
},
|
||||
{
|
||||
"filename": "ReactTestRenderer-dev.js",
|
||||
@@ -326,29 +326,29 @@
|
||||
"filename": "react-test-renderer-shallow.development.js",
|
||||
"bundleType": "UMD_DEV",
|
||||
"packageName": "react-test-renderer",
|
||||
"size": 38084,
|
||||
"gzip": 9724
|
||||
"size": 26400,
|
||||
"gzip": 7200
|
||||
},
|
||||
{
|
||||
"filename": "react-test-renderer-shallow.production.min.js",
|
||||
"bundleType": "UMD_PROD",
|
||||
"packageName": "react-test-renderer",
|
||||
"size": 11382,
|
||||
"gzip": 3422
|
||||
"size": 7442,
|
||||
"gzip": 2425
|
||||
},
|
||||
{
|
||||
"filename": "react-test-renderer-shallow.development.js",
|
||||
"bundleType": "NODE_DEV",
|
||||
"packageName": "react-test-renderer",
|
||||
"size": 32246,
|
||||
"gzip": 8323
|
||||
"size": 20656,
|
||||
"gzip": 5736
|
||||
},
|
||||
{
|
||||
"filename": "react-test-renderer-shallow.production.min.js",
|
||||
"bundleType": "NODE_PROD",
|
||||
"packageName": "react-test-renderer",
|
||||
"size": 12043,
|
||||
"gzip": 3734
|
||||
"size": 8141,
|
||||
"gzip": 2697
|
||||
},
|
||||
{
|
||||
"filename": "ReactShallowRenderer-dev.js",
|
||||
@@ -361,57 +361,57 @@
|
||||
"filename": "react-noop-renderer.development.js",
|
||||
"bundleType": "NODE_DEV",
|
||||
"packageName": "react-noop-renderer",
|
||||
"size": 32858,
|
||||
"gzip": 7514
|
||||
"size": 28829,
|
||||
"gzip": 6286
|
||||
},
|
||||
{
|
||||
"filename": "react-noop-renderer.production.min.js",
|
||||
"bundleType": "NODE_PROD",
|
||||
"packageName": "react-noop-renderer",
|
||||
"size": 11486,
|
||||
"gzip": 3766
|
||||
"size": 10889,
|
||||
"gzip": 3632
|
||||
},
|
||||
{
|
||||
"filename": "react-reconciler.development.js",
|
||||
"bundleType": "NODE_DEV",
|
||||
"packageName": "react-reconciler",
|
||||
"size": 481582,
|
||||
"gzip": 101249
|
||||
"size": 435777,
|
||||
"gzip": 93120
|
||||
},
|
||||
{
|
||||
"filename": "react-reconciler.production.min.js",
|
||||
"bundleType": "NODE_PROD",
|
||||
"packageName": "react-reconciler",
|
||||
"size": 65118,
|
||||
"gzip": 19259
|
||||
"size": 57202,
|
||||
"gzip": 17158
|
||||
},
|
||||
{
|
||||
"filename": "react-reconciler-persistent.development.js",
|
||||
"bundleType": "NODE_DEV",
|
||||
"packageName": "react-reconciler",
|
||||
"size": 479920,
|
||||
"gzip": 100594
|
||||
"size": 434187,
|
||||
"gzip": 92481
|
||||
},
|
||||
{
|
||||
"filename": "react-reconciler-persistent.production.min.js",
|
||||
"bundleType": "NODE_PROD",
|
||||
"packageName": "react-reconciler",
|
||||
"size": 65129,
|
||||
"gzip": 19264
|
||||
"size": 57213,
|
||||
"gzip": 17164
|
||||
},
|
||||
{
|
||||
"filename": "react-reconciler-reflection.development.js",
|
||||
"bundleType": "NODE_DEV",
|
||||
"packageName": "react-reconciler",
|
||||
"size": 16132,
|
||||
"gzip": 5091
|
||||
"size": 15764,
|
||||
"gzip": 4943
|
||||
},
|
||||
{
|
||||
"filename": "react-reconciler-reflection.production.min.js",
|
||||
"bundleType": "NODE_PROD",
|
||||
"packageName": "react-reconciler",
|
||||
"size": 2757,
|
||||
"gzip": 1247
|
||||
"size": 2614,
|
||||
"gzip": 1153
|
||||
},
|
||||
{
|
||||
"filename": "react-call-return.development.js",
|
||||
@@ -431,29 +431,29 @@
|
||||
"filename": "react-is.development.js",
|
||||
"bundleType": "UMD_DEV",
|
||||
"packageName": "react-is",
|
||||
"size": 8255,
|
||||
"gzip": 2495
|
||||
"size": 7691,
|
||||
"gzip": 2393
|
||||
},
|
||||
{
|
||||
"filename": "react-is.production.min.js",
|
||||
"bundleType": "UMD_PROD",
|
||||
"packageName": "react-is",
|
||||
"size": 2342,
|
||||
"gzip": 898
|
||||
"size": 2171,
|
||||
"gzip": 854
|
||||
},
|
||||
{
|
||||
"filename": "react-is.development.js",
|
||||
"bundleType": "NODE_DEV",
|
||||
"packageName": "react-is",
|
||||
"size": 8066,
|
||||
"gzip": 2445
|
||||
"size": 7502,
|
||||
"gzip": 2344
|
||||
},
|
||||
{
|
||||
"filename": "react-is.production.min.js",
|
||||
"bundleType": "NODE_PROD",
|
||||
"packageName": "react-is",
|
||||
"size": 2339,
|
||||
"gzip": 838
|
||||
"size": 2132,
|
||||
"gzip": 793
|
||||
},
|
||||
{
|
||||
"filename": "ReactIs-dev.js",
|
||||
@@ -501,36 +501,36 @@
|
||||
"filename": "React-dev.js",
|
||||
"bundleType": "FB_WWW_DEV",
|
||||
"packageName": "react",
|
||||
"size": 60700,
|
||||
"gzip": 16126
|
||||
"size": 58338,
|
||||
"gzip": 15703
|
||||
},
|
||||
{
|
||||
"filename": "React-prod.js",
|
||||
"bundleType": "FB_WWW_PROD",
|
||||
"packageName": "react",
|
||||
"size": 15734,
|
||||
"gzip": 4189
|
||||
"size": 15346,
|
||||
"gzip": 4131
|
||||
},
|
||||
{
|
||||
"filename": "ReactDOM-dev.js",
|
||||
"bundleType": "FB_WWW_DEV",
|
||||
"packageName": "react-dom",
|
||||
"size": 801709,
|
||||
"gzip": 178348
|
||||
"size": 742241,
|
||||
"gzip": 167544
|
||||
},
|
||||
{
|
||||
"filename": "ReactDOM-prod.js",
|
||||
"bundleType": "FB_WWW_PROD",
|
||||
"packageName": "react-dom",
|
||||
"size": 329235,
|
||||
"gzip": 60001
|
||||
"size": 317018,
|
||||
"gzip": 58396
|
||||
},
|
||||
{
|
||||
"filename": "ReactTestUtils-dev.js",
|
||||
"bundleType": "FB_WWW_DEV",
|
||||
"packageName": "react-dom",
|
||||
"size": 44795,
|
||||
"gzip": 12159
|
||||
"size": 42313,
|
||||
"gzip": 11425
|
||||
},
|
||||
{
|
||||
"filename": "ReactDOMUnstableNativeDependencies-dev.js",
|
||||
@@ -550,113 +550,113 @@
|
||||
"filename": "ReactDOMServer-dev.js",
|
||||
"bundleType": "FB_WWW_DEV",
|
||||
"packageName": "react-dom",
|
||||
"size": 126805,
|
||||
"gzip": 33138
|
||||
"size": 119227,
|
||||
"gzip": 31112
|
||||
},
|
||||
{
|
||||
"filename": "ReactDOMServer-prod.js",
|
||||
"bundleType": "FB_WWW_PROD",
|
||||
"packageName": "react-dom",
|
||||
"size": 46040,
|
||||
"gzip": 10601
|
||||
"size": 41626,
|
||||
"gzip": 9808
|
||||
},
|
||||
{
|
||||
"filename": "ReactART-dev.js",
|
||||
"bundleType": "FB_WWW_DEV",
|
||||
"packageName": "react-art",
|
||||
"size": 493866,
|
||||
"gzip": 102238
|
||||
"size": 445295,
|
||||
"gzip": 93558
|
||||
},
|
||||
{
|
||||
"filename": "ReactART-prod.js",
|
||||
"bundleType": "FB_WWW_PROD",
|
||||
"packageName": "react-art",
|
||||
"size": 199704,
|
||||
"gzip": 33787
|
||||
"size": 187912,
|
||||
"gzip": 32090
|
||||
},
|
||||
{
|
||||
"filename": "ReactNativeRenderer-dev.js",
|
||||
"bundleType": "RN_FB_DEV",
|
||||
"packageName": "react-native-renderer",
|
||||
"size": 621398,
|
||||
"gzip": 133323
|
||||
"size": 573493,
|
||||
"gzip": 124971
|
||||
},
|
||||
{
|
||||
"filename": "ReactNativeRenderer-prod.js",
|
||||
"bundleType": "RN_FB_PROD",
|
||||
"packageName": "react-native-renderer",
|
||||
"size": 252107,
|
||||
"gzip": 44030
|
||||
"size": 244566,
|
||||
"gzip": 43021
|
||||
},
|
||||
{
|
||||
"filename": "ReactNativeRenderer-dev.js",
|
||||
"bundleType": "RN_OSS_DEV",
|
||||
"packageName": "react-native-renderer",
|
||||
"size": 621309,
|
||||
"gzip": 133286
|
||||
"size": 573182,
|
||||
"gzip": 124874
|
||||
},
|
||||
{
|
||||
"filename": "ReactNativeRenderer-prod.js",
|
||||
"bundleType": "RN_OSS_PROD",
|
||||
"packageName": "react-native-renderer",
|
||||
"size": 252121,
|
||||
"gzip": 44026
|
||||
"size": 229659,
|
||||
"gzip": 39880
|
||||
},
|
||||
{
|
||||
"filename": "ReactFabric-dev.js",
|
||||
"bundleType": "RN_FB_DEV",
|
||||
"packageName": "react-native-renderer",
|
||||
"size": 612032,
|
||||
"gzip": 130990
|
||||
"size": 563576,
|
||||
"gzip": 122418
|
||||
},
|
||||
{
|
||||
"filename": "ReactFabric-prod.js",
|
||||
"bundleType": "RN_FB_PROD",
|
||||
"packageName": "react-native-renderer",
|
||||
"size": 244266,
|
||||
"gzip": 42532
|
||||
"size": 223730,
|
||||
"gzip": 38540
|
||||
},
|
||||
{
|
||||
"filename": "ReactFabric-dev.js",
|
||||
"bundleType": "RN_OSS_DEV",
|
||||
"packageName": "react-native-renderer",
|
||||
"size": 611935,
|
||||
"gzip": 130940
|
||||
"size": 563611,
|
||||
"gzip": 122433
|
||||
},
|
||||
{
|
||||
"filename": "ReactFabric-prod.js",
|
||||
"bundleType": "RN_OSS_PROD",
|
||||
"packageName": "react-native-renderer",
|
||||
"size": 244272,
|
||||
"gzip": 42522
|
||||
"size": 223766,
|
||||
"gzip": 38555
|
||||
},
|
||||
{
|
||||
"filename": "ReactTestRenderer-dev.js",
|
||||
"bundleType": "FB_WWW_DEV",
|
||||
"packageName": "react-test-renderer",
|
||||
"size": 501300,
|
||||
"gzip": 103689
|
||||
"size": 453557,
|
||||
"gzip": 95443
|
||||
},
|
||||
{
|
||||
"filename": "ReactShallowRenderer-dev.js",
|
||||
"bundleType": "FB_WWW_DEV",
|
||||
"packageName": "react-test-renderer",
|
||||
"size": 30628,
|
||||
"gzip": 7749
|
||||
"size": 18564,
|
||||
"gzip": 4859
|
||||
},
|
||||
{
|
||||
"filename": "ReactIs-dev.js",
|
||||
"bundleType": "FB_WWW_DEV",
|
||||
"packageName": "react-is",
|
||||
"size": 6437,
|
||||
"gzip": 1724
|
||||
"size": 5873,
|
||||
"gzip": 1621
|
||||
},
|
||||
{
|
||||
"filename": "ReactIs-prod.js",
|
||||
"bundleType": "FB_WWW_PROD",
|
||||
"packageName": "react-is",
|
||||
"size": 4838,
|
||||
"gzip": 1202
|
||||
"size": 4382,
|
||||
"gzip": 1135
|
||||
},
|
||||
{
|
||||
"filename": "scheduler.development.js",
|
||||
@@ -676,15 +676,15 @@
|
||||
"filename": "scheduler.development.js",
|
||||
"bundleType": "NODE_DEV",
|
||||
"packageName": "scheduler",
|
||||
"size": 23870,
|
||||
"gzip": 6174
|
||||
"size": 22073,
|
||||
"gzip": 5976
|
||||
},
|
||||
{
|
||||
"filename": "scheduler.production.min.js",
|
||||
"bundleType": "NODE_PROD",
|
||||
"packageName": "scheduler",
|
||||
"size": 5000,
|
||||
"gzip": 1883
|
||||
"size": 4755,
|
||||
"gzip": 1865
|
||||
},
|
||||
{
|
||||
"filename": "SimpleCacheProvider-dev.js",
|
||||
@@ -704,50 +704,50 @@
|
||||
"filename": "react-noop-renderer-persistent.development.js",
|
||||
"bundleType": "NODE_DEV",
|
||||
"packageName": "react-noop-renderer",
|
||||
"size": 32977,
|
||||
"gzip": 7525
|
||||
"size": 28948,
|
||||
"gzip": 6299
|
||||
},
|
||||
{
|
||||
"filename": "react-noop-renderer-persistent.production.min.js",
|
||||
"bundleType": "NODE_PROD",
|
||||
"packageName": "react-noop-renderer",
|
||||
"size": 11508,
|
||||
"gzip": 3772
|
||||
"size": 10911,
|
||||
"gzip": 3639
|
||||
},
|
||||
{
|
||||
"filename": "react-dom.profiling.min.js",
|
||||
"bundleType": "NODE_PROFILING",
|
||||
"packageName": "react-dom",
|
||||
"size": 111156,
|
||||
"gzip": 35048
|
||||
"size": 102985,
|
||||
"gzip": 32673
|
||||
},
|
||||
{
|
||||
"filename": "ReactNativeRenderer-profiling.js",
|
||||
"bundleType": "RN_OSS_PROFILING",
|
||||
"packageName": "react-native-renderer",
|
||||
"size": 258626,
|
||||
"gzip": 45614
|
||||
"size": 235342,
|
||||
"gzip": 41248
|
||||
},
|
||||
{
|
||||
"filename": "ReactFabric-profiling.js",
|
||||
"bundleType": "RN_OSS_PROFILING",
|
||||
"packageName": "react-native-renderer",
|
||||
"size": 250654,
|
||||
"gzip": 44086
|
||||
"size": 228530,
|
||||
"gzip": 39962
|
||||
},
|
||||
{
|
||||
"filename": "Scheduler-dev.js",
|
||||
"bundleType": "FB_WWW_DEV",
|
||||
"packageName": "scheduler",
|
||||
"size": 24123,
|
||||
"gzip": 6223
|
||||
"size": 22314,
|
||||
"gzip": 6027
|
||||
},
|
||||
{
|
||||
"filename": "Scheduler-prod.js",
|
||||
"bundleType": "FB_WWW_PROD",
|
||||
"packageName": "scheduler",
|
||||
"size": 14327,
|
||||
"gzip": 2953
|
||||
"size": 13375,
|
||||
"gzip": 2927
|
||||
},
|
||||
{
|
||||
"filename": "react.profiling.min.js",
|
||||
@@ -760,50 +760,50 @@
|
||||
"filename": "React-profiling.js",
|
||||
"bundleType": "FB_WWW_PROFILING",
|
||||
"packageName": "react",
|
||||
"size": 15734,
|
||||
"gzip": 4189
|
||||
"size": 15346,
|
||||
"gzip": 4131
|
||||
},
|
||||
{
|
||||
"filename": "ReactDOM-profiling.js",
|
||||
"bundleType": "FB_WWW_PROFILING",
|
||||
"packageName": "react-dom",
|
||||
"size": 335969,
|
||||
"gzip": 61519
|
||||
"size": 323954,
|
||||
"gzip": 59824
|
||||
},
|
||||
{
|
||||
"filename": "ReactNativeRenderer-profiling.js",
|
||||
"bundleType": "RN_FB_PROFILING",
|
||||
"packageName": "react-native-renderer",
|
||||
"size": 258607,
|
||||
"gzip": 45621
|
||||
"size": 250505,
|
||||
"gzip": 44397
|
||||
},
|
||||
{
|
||||
"filename": "ReactFabric-profiling.js",
|
||||
"bundleType": "RN_FB_PROFILING",
|
||||
"packageName": "react-native-renderer",
|
||||
"size": 250643,
|
||||
"gzip": 44092
|
||||
"size": 228489,
|
||||
"gzip": 39945
|
||||
},
|
||||
{
|
||||
"filename": "react.profiling.min.js",
|
||||
"bundleType": "UMD_PROFILING",
|
||||
"packageName": "react",
|
||||
"size": 14756,
|
||||
"gzip": 5369
|
||||
"size": 13977,
|
||||
"gzip": 5211
|
||||
},
|
||||
{
|
||||
"filename": "react-dom.profiling.min.js",
|
||||
"bundleType": "UMD_PROFILING",
|
||||
"packageName": "react-dom",
|
||||
"size": 110830,
|
||||
"gzip": 35633
|
||||
"size": 102894,
|
||||
"gzip": 33243
|
||||
},
|
||||
{
|
||||
"filename": "scheduler-tracing.development.js",
|
||||
"bundleType": "NODE_DEV",
|
||||
"packageName": "scheduler",
|
||||
"size": 10554,
|
||||
"gzip": 2432
|
||||
"size": 10480,
|
||||
"gzip": 2403
|
||||
},
|
||||
{
|
||||
"filename": "scheduler-tracing.production.min.js",
|
||||
@@ -823,8 +823,8 @@
|
||||
"filename": "SchedulerTracing-dev.js",
|
||||
"bundleType": "FB_WWW_DEV",
|
||||
"packageName": "scheduler",
|
||||
"size": 10121,
|
||||
"gzip": 2117
|
||||
"size": 10467,
|
||||
"gzip": 2295
|
||||
},
|
||||
{
|
||||
"filename": "SchedulerTracing-prod.js",
|
||||
@@ -844,43 +844,43 @@
|
||||
"filename": "react-cache.development.js",
|
||||
"bundleType": "NODE_DEV",
|
||||
"packageName": "react-cache",
|
||||
"size": 9030,
|
||||
"gzip": 3016
|
||||
"size": 9015,
|
||||
"gzip": 3022
|
||||
},
|
||||
{
|
||||
"filename": "react-cache.production.min.js",
|
||||
"bundleType": "NODE_PROD",
|
||||
"packageName": "react-cache",
|
||||
"size": 2199,
|
||||
"gzip": 1125
|
||||
"size": 2204,
|
||||
"gzip": 1128
|
||||
},
|
||||
{
|
||||
"filename": "ReactCache-dev.js",
|
||||
"bundleType": "FB_WWW_DEV",
|
||||
"packageName": "react-cache",
|
||||
"size": 7429,
|
||||
"gzip": 2370
|
||||
"size": 7409,
|
||||
"gzip": 2374
|
||||
},
|
||||
{
|
||||
"filename": "ReactCache-prod.js",
|
||||
"bundleType": "FB_WWW_PROD",
|
||||
"packageName": "react-cache",
|
||||
"size": 5200,
|
||||
"gzip": 1641
|
||||
"size": 5180,
|
||||
"gzip": 1645
|
||||
},
|
||||
{
|
||||
"filename": "react-cache.development.js",
|
||||
"bundleType": "UMD_DEV",
|
||||
"packageName": "react-cache",
|
||||
"size": 9261,
|
||||
"gzip": 3090
|
||||
"size": 9246,
|
||||
"gzip": 3094
|
||||
},
|
||||
{
|
||||
"filename": "react-cache.production.min.js",
|
||||
"bundleType": "UMD_PROD",
|
||||
"packageName": "react-cache",
|
||||
"size": 2398,
|
||||
"gzip": 1215
|
||||
"size": 2403,
|
||||
"gzip": 1219
|
||||
},
|
||||
{
|
||||
"filename": "jest-react.development.js",
|
||||
@@ -914,22 +914,22 @@
|
||||
"filename": "react-debug-tools.development.js",
|
||||
"bundleType": "NODE_DEV",
|
||||
"packageName": "react-debug-tools",
|
||||
"size": 19024,
|
||||
"gzip": 5638
|
||||
"size": 16717,
|
||||
"gzip": 4965
|
||||
},
|
||||
{
|
||||
"filename": "react-debug-tools.production.min.js",
|
||||
"bundleType": "NODE_PROD",
|
||||
"packageName": "react-debug-tools",
|
||||
"size": 5800,
|
||||
"gzip": 2343
|
||||
"size": 5402,
|
||||
"gzip": 2187
|
||||
},
|
||||
{
|
||||
"filename": "eslint-plugin-react-hooks.development.js",
|
||||
"bundleType": "NODE_DEV",
|
||||
"packageName": "eslint-plugin-react-hooks",
|
||||
"size": 26115,
|
||||
"gzip": 6005
|
||||
"size": 26113,
|
||||
"gzip": 6008
|
||||
},
|
||||
{
|
||||
"filename": "eslint-plugin-react-hooks.production.min.js",
|
||||
@@ -1026,99 +1026,8 @@
|
||||
"filename": "ESLintPluginReactHooks-dev.js",
|
||||
"bundleType": "FB_WWW_DEV",
|
||||
"packageName": "eslint-plugin-react-hooks",
|
||||
"size": 27788,
|
||||
"gzip": 6145
|
||||
},
|
||||
{
|
||||
"filename": "react-dom-unstable-fire.development.js",
|
||||
"bundleType": "UMD_DEV",
|
||||
"packageName": "react-dom",
|
||||
"size": 784046,
|
||||
"gzip": 178758
|
||||
},
|
||||
{
|
||||
"filename": "react-dom-unstable-fire.production.min.js",
|
||||
"bundleType": "UMD_PROD",
|
||||
"packageName": "react-dom",
|
||||
"size": 107857,
|
||||
"gzip": 34738
|
||||
},
|
||||
{
|
||||
"filename": "react-dom-unstable-fire.profiling.min.js",
|
||||
"bundleType": "UMD_PROFILING",
|
||||
"packageName": "react-dom",
|
||||
"size": 110845,
|
||||
"gzip": 35642
|
||||
},
|
||||
{
|
||||
"filename": "react-dom-unstable-fire.development.js",
|
||||
"bundleType": "NODE_DEV",
|
||||
"packageName": "react-dom",
|
||||
"size": 778520,
|
||||
"gzip": 177227
|
||||
},
|
||||
{
|
||||
"filename": "react-dom-unstable-fire.production.min.js",
|
||||
"bundleType": "NODE_PROD",
|
||||
"packageName": "react-dom",
|
||||
"size": 108023,
|
||||
"gzip": 34220
|
||||
},
|
||||
{
|
||||
"filename": "react-dom-unstable-fire.profiling.min.js",
|
||||
"bundleType": "NODE_PROFILING",
|
||||
"packageName": "react-dom",
|
||||
"size": 111170,
|
||||
"gzip": 35058
|
||||
},
|
||||
{
|
||||
"filename": "ReactFire-dev.js",
|
||||
"bundleType": "FB_WWW_DEV",
|
||||
"packageName": "react-dom",
|
||||
"size": 800900,
|
||||
"gzip": 178268
|
||||
},
|
||||
{
|
||||
"filename": "ReactFire-prod.js",
|
||||
"bundleType": "FB_WWW_PROD",
|
||||
"packageName": "react-dom",
|
||||
"size": 317385,
|
||||
"gzip": 57621
|
||||
},
|
||||
{
|
||||
"filename": "ReactFire-profiling.js",
|
||||
"bundleType": "FB_WWW_PROFILING",
|
||||
"packageName": "react-dom",
|
||||
"size": 324156,
|
||||
"gzip": 59066
|
||||
},
|
||||
{
|
||||
"filename": "jest-mock-scheduler.development.js",
|
||||
"bundleType": "NODE_DEV",
|
||||
"packageName": "jest-mock-scheduler",
|
||||
"size": 1533,
|
||||
"gzip": 724
|
||||
},
|
||||
{
|
||||
"filename": "jest-mock-scheduler.production.min.js",
|
||||
"bundleType": "NODE_PROD",
|
||||
"packageName": "jest-mock-scheduler",
|
||||
"size": 671,
|
||||
"gzip": 437
|
||||
},
|
||||
{
|
||||
"filename": "JestMockScheduler-dev.js",
|
||||
"bundleType": "FB_WWW_DEV",
|
||||
"packageName": "jest-mock-scheduler",
|
||||
"size": 1511,
|
||||
"gzip": 711
|
||||
},
|
||||
{
|
||||
"filename": "JestMockScheduler-prod.js",
|
||||
"bundleType": "FB_WWW_PROD",
|
||||
"packageName": "jest-mock-scheduler",
|
||||
"size": 1085,
|
||||
"gzip": 532
|
||||
"size": 27786,
|
||||
"gzip": 6149
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user