Compare commits
75 Commits
v16.3.0-al
...
v16.3.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dc059579c3 | ||
|
|
787b343f67 | ||
|
|
2279843ef9 | ||
|
|
a2cc3c38e2 | ||
|
|
36c2939372 | ||
|
|
0f2f90bd9a | ||
|
|
da4e85567b | ||
|
|
eb6e752cab | ||
|
|
ba245f6f9b | ||
|
|
6f2ea73978 | ||
|
|
4ccf58a94d | ||
|
|
7a27ebd52a | ||
|
|
6b99c6f9d3 | ||
|
|
59dac9d7a6 | ||
|
|
0c80977061 | ||
|
|
fa8e67893f | ||
|
|
59b39056d9 | ||
|
|
18ba36d891 | ||
|
|
43044757e5 | ||
|
|
2c3f5fb97b | ||
|
|
8e3d94ffa1 | ||
|
|
9778873143 | ||
|
|
b2379d4cbe | ||
|
|
6294b67a40 | ||
|
|
8650d2a135 | ||
|
|
53fdc19df0 | ||
|
|
96fe3b1be2 | ||
|
|
125dd16ba0 | ||
|
|
15e3dffb4c | ||
|
|
5855e9f215 | ||
|
|
7a833dad95 | ||
|
|
c44665e832 | ||
|
|
268a3f60df | ||
|
|
c1b21a746c | ||
|
|
488ad5a6b9 | ||
|
|
c2c3c0cc36 | ||
|
|
b3d883630c | ||
|
|
80ddd15b72 | ||
|
|
61444a415b | ||
|
|
ff32420e57 | ||
|
|
ad5273d348 | ||
|
|
90c41a2e56 | ||
|
|
718d0d21f2 | ||
|
|
e1a106a071 | ||
|
|
e9ba8ec866 | ||
|
|
dadafd6bd8 | ||
|
|
7d31311de3 | ||
|
|
1bab82a9de | ||
|
|
cc616b01fc | ||
|
|
1a71c4de13 | ||
|
|
cafee5cb2f | ||
|
|
3cdb5780d4 | ||
|
|
02d4e5dd39 | ||
|
|
8c20615b06 | ||
|
|
c1308adb4b | ||
|
|
0af384b4c3 | ||
|
|
12687ff331 | ||
|
|
dcbb4301f0 | ||
|
|
40fa616053 | ||
|
|
f94a6b4fed | ||
|
|
dc48326cd5 | ||
|
|
c6b7cea343 | ||
|
|
3553489f7b | ||
|
|
3ed6483e14 | ||
|
|
f9377c1762 | ||
|
|
33eddbc0c8 | ||
|
|
ab4dc50146 | ||
|
|
9d484edc4b | ||
|
|
8d09422424 | ||
|
|
e1ff342bf7 | ||
|
|
7e87df8090 | ||
|
|
208b490ed9 | ||
|
|
2738e84805 | ||
|
|
d38616d693 | ||
|
|
ced176edb7 |
55
CHANGELOG.md
55
CHANGELOG.md
@@ -4,25 +4,78 @@
|
||||
Changes that have landed in master but are not yet released.
|
||||
Click to see more.
|
||||
</summary>
|
||||
</details>
|
||||
|
||||
## 16.3.0 (March 29, 2018)
|
||||
|
||||
### React
|
||||
|
||||
* Add a new officially supported context API. ([@acdlite](https://github.com/acdlite) in [#11818](https://github.com/facebook/react/pull/11818))
|
||||
* Add a new `React.createRef()` API as an ergonomic alternative to callback refs. ([@trueadm](https://github.com/trueadm) in [#12162](https://github.com/facebook/react/pull/12162))
|
||||
* Add a new `React.forwardRef()` API to let components forward their refs to a child. ([@bvaughn](https://github.com/bvaughn) in [#12346](https://github.com/facebook/react/pull/12346))
|
||||
* Fix a false positive warning in IE11 when using `React.Fragment`. ([@XaveScor](https://github.com/XaveScor) in [#11823](https://github.com/facebook/react/pull/11823))
|
||||
* Replace `React.unstable_AsyncComponent` with `React.unstable_AsyncMode`. ([@acdlite](https://github.com/acdlite) in [#12117](https://github.com/facebook/react/pull/12117))
|
||||
* Improve the error message when calling `setState()` on an unmounted component. ([@sophiebits](https://github.com/sophiebits) in [#12347](https://github.com/facebook/react/pull/12347))
|
||||
|
||||
### React DOM
|
||||
|
||||
* Add a new `getDerivedStateFromProps()` lifecycle and `UNSAFE_` aliases for the legacy lifecycles. ([@bvaughn](https://github.com/bvaughn) in [#12028](https://github.com/facebook/react/pull/12028))
|
||||
* Add a new `getSnapshotBeforeUpdate()` lifecycle. ([@bvaughn](https://github.com/bvaughn) in [#12404](https://github.com/facebook/react/pull/12404))
|
||||
* Add a new `<React.StrictMode>` wrapper to help prepare apps for async rendering. ([@bvaughn](https://github.com/bvaughn) in [#12083](https://github.com/facebook/react/pull/12083))
|
||||
* Add support for `onLoad` and `onError` events on the `<link>` tag. ([@roderickhsiao](https://github.com/roderickhsiao) in [#11825](https://github.com/facebook/react/pull/11825))
|
||||
* Add support for `noModule` boolean attribute on the `<script>` tag. ([@aweary](https://github.com/aweary) in [#11900](https://github.com/facebook/react/pull/11900))
|
||||
* Fix minor DOM input bugs in IE and Safari. ([@nhunzaker](https://github.com/nhunzaker) in [#11534](https://github.com/facebook/react/pull/11534))
|
||||
* Correctly detect Ctrl + Enter in `onKeyPress` in more browsers. ([@nstraub](https://github.com/nstraub) in [#10514](https://github.com/facebook/react/pull/10514))
|
||||
* Fix containing elements getting focused on SSR markup mismatch. ([@koba04](https://github.com/koba04) in [#11737](https://github.com/facebook/react/pull/11737))
|
||||
* Fix `value` and `defaultValue` to ignore Symbol values. ([@nhunzaker](https://github.com/nhunzaker) in [#11741](https://github.com/facebook/react/pull/11741))
|
||||
* Fix refs to class components not getting cleaned up when the attribute is removed. ([@bvaughn](https://github.com/bvaughn) in [#12178](https://github.com/facebook/react/pull/12178))
|
||||
* Fix an IE/Edge issue when rendering inputs into a different window. ([@M-ZubairAhmed](https://github.com/M-ZubairAhmed) in [#11870](https://github.com/facebook/react/pull/11870))
|
||||
* Throw with a meaningful message if the component runs after jsdom has been destroyed. ([@gaearon](https://github.com/gaearon) in [#11677](https://github.com/facebook/react/pull/11677))
|
||||
* Don't crash if there is a global variable called `opera` with a `null` value. [@alisherdavronov](https://github.com/alisherdavronov) in [#11854](https://github.com/facebook/react/pull/11854))
|
||||
* Don't check for old versions of Opera. ([@skiritsis](https://github.com/skiritsis) in [#11921](https://github.com/facebook/react/pull/11921))
|
||||
* Deduplicate warning messages about `<option selected>`. ([@watadarkstar](https://github.com/watadarkstar) in [#11821](https://github.com/facebook/react/pull/11821))
|
||||
* Deduplicate warning messages about invalid callback. ([@yenshih](https://github.com/yenshih) in [#11833](https://github.com/facebook/react/pull/11833))
|
||||
* Deprecate `ReactDOM.unstable_createPortal()` in favor of `ReactDOM.createPortal()`. ([@prometheansacrifice](https://github.com/prometheansacrifice) in [#11747](https://github.com/facebook/react/pull/11747))
|
||||
* Don't emit User Timing entries for context types. ([@abhaynikam](https://github.com/abhaynikam) in [#12250](https://github.com/facebook/react/pull/12250))
|
||||
* Improve the error message when context consumer child isn't a function. ([@raunofreiberg](https://github.com/raunofreiberg) in [#12267](https://github.com/facebook/react/pull/12267))
|
||||
* Improve the error message when adding a ref to a functional component. ([@skiritsis](https://github.com/skiritsis) in [#11782](https://github.com/facebook/react/pull/11782))
|
||||
|
||||
### React DOM Server
|
||||
|
||||
* Prevent an infinite loop when attempting to render portals with SSR. ([@gaearon](https://github.com/gaearon) in [#11709](https://github.com/facebook/react/pull/11709))
|
||||
* Warn if a class doesn't extend `React.Component`. ([@wyze](https://github.com/wyze) in [#11993](https://github.com/facebook/react/pull/11993))
|
||||
* Fix an issue with `this.state` of different components getting mixed up. ([@sophiebits](https://github.com/sophiebits) in [#12323](https://github.com/facebook/react/pull/12323))
|
||||
* Provide a better message when component type is undefined. ([@HeroProtagonist](https://github.com/HeroProtagonist) in [#11966](https://github.com/facebook/react/pull/11966))
|
||||
|
||||
</details>
|
||||
## React Test Renderer
|
||||
|
||||
* Fix handling of fragments in `toTree()`. ([@maciej-ka](https://github.com/maciej-ka) in [#12107](https://github.com/facebook/react/pull/12107) and [@gaearon](https://github.com/gaearon) in [#12154](https://github.com/facebook/react/pull/12154))
|
||||
* Shallow renderer should assign state to `null` for components that don't set it. ([@jwbay](https://github.com/jwbay) in [#11965](https://github.com/facebook/react/pull/11965))
|
||||
* Shallow renderer should filter legacy context according to `contextTypes`. ([@koba04](https://github.com/koba04) in [#11922](https://github.com/facebook/react/pull/11922))
|
||||
* Add an unstable API for testing asynchronous rendering. ([@acdlite](https://github.com/acdlite) in [#12478](https://github.com/facebook/react/pull/12478))
|
||||
|
||||
### React Is (New)
|
||||
|
||||
* First release of the [new package](https://github.com/facebook/react/tree/master/packages/react-is) that libraries can use to detect different React node types. ([@bvaughn](https://github.com/bvaughn) in [#12199](https://github.com/facebook/react/pull/12199))
|
||||
* Add `ReactIs.isValidElementType()` to help higher-order components validate their inputs. ([@jamesreggio](https://github.com/jamesreggio) in [#12483](https://github.com/facebook/react/pull/12483))
|
||||
|
||||
### React Lifecycles Compat (New)
|
||||
|
||||
* First release of the [new package](https://github.com/reactjs/react-lifecycles-compat) to help library developers target multiple versions of React. ([@bvaughn](https://github.com/bvaughn) in [#12105](https://github.com/facebook/react/pull/12105))
|
||||
|
||||
### Create Subscription (New)
|
||||
|
||||
* First release of the [new package](https://github.com/facebook/react/tree/master/packages/create-subscription) to subscribe to external data sources safely for async rendering. ([@bvaughn](https://github.com/bvaughn) in [#12325](https://github.com/facebook/react/pull/12325))
|
||||
|
||||
### React Reconciler (Experimental)
|
||||
|
||||
* Expose `react-reconciler/persistent` for building renderers that use persistent data structures. ([@gaearon](https://github.com/gaearon) in [#12156](https://github.com/facebook/react/pull/12156))
|
||||
* Pass host context to `finalizeInitialChildren()`. ([@jquense](https://github.com/jquense) in [#11970](https://github.com/facebook/react/pull/11970))
|
||||
* Remove `useSyncScheduling` from the host config. ([@acdlite](https://github.com/acdlite) in [#11771](https://github.com/facebook/react/pull/11771))
|
||||
|
||||
### React Call Return (Experimental)
|
||||
|
||||
* Fix a crash on updates. ([@rmhartog](https://github.com/rmhartog) in [#11955](https://github.com/facebook/react/pull/11955))
|
||||
|
||||
## 16.2.0 (November 28, 2017)
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"private": true,
|
||||
"version": "16.3.0-alpha.2",
|
||||
"version": "16.3.1",
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
],
|
||||
@@ -93,6 +93,7 @@
|
||||
"rollup-plugin-replace": "^2.0.0",
|
||||
"rollup-plugin-strip-banner": "^0.2.0",
|
||||
"run-sequence": "^1.1.4",
|
||||
"semver": "^5.5.0",
|
||||
"targz": "^1.0.1",
|
||||
"through2": "^2.0.0",
|
||||
"tmp": "~0.0.28",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# create-subscription
|
||||
|
||||
`create-subscription` provides an async-safe interface to manage a subscription.
|
||||
`create-subscription` is a utility for subscribing to external data sources inside React components. It is officially supported and maintained by the React team.
|
||||
|
||||
## When should you NOT use this?
|
||||
|
||||
@@ -11,6 +11,16 @@ Other cases have **better long-term solutions**:
|
||||
* I/O subscriptions (e.g. notifications) that update infrequently should use [`simple-cache-provider`](https://github.com/facebook/react/blob/master/packages/simple-cache-provider/README.md) instead.
|
||||
* Complex libraries like Relay/Apollo should manage subscriptions manually with the same techniques which this library uses under the hood (as referenced [here](https://gist.github.com/bvaughn/d569177d70b50b58bff69c3c4a5353f3)) in a way that is most optimized for their library usage.
|
||||
|
||||
## Limitations in async mode
|
||||
|
||||
The main motivation for `create-subscription` is to provide a way for library authors to ensure compatibility with React's upcoming asynchronous rendering mode. `create-subscription` guarantees correctness in async mode, accounting for the subtle bugs and edge cases that a library author might otherwise miss.
|
||||
|
||||
However, it achieves correctness by sometimes de-opting to synchronous mode, obviating the benefits of async rendering. This is an inherent limitation of storing state outside of React's managed state queue and rendering in response to a change event.
|
||||
|
||||
The effect of de-opting to sync mode is that the main thread may periodically be blocked (in the case of CPU-bound work), and placeholders may appear earlier than desired (in the case of IO-bound work).
|
||||
|
||||
For **full compatibility** with asynchronous rendering, including both **time-slicing** and **React Suspense**, the suggested longer term solution is to move to one of the patterns described in the previous section.
|
||||
|
||||
## What types of subscriptions can this support?
|
||||
|
||||
This abstraction can handle a variety of subscription types, including:
|
||||
@@ -50,7 +60,7 @@ const Subscription = createSubscription({
|
||||
});
|
||||
```
|
||||
|
||||
To use the `Subscription` component, pass the subscribable property (e.g. an event dispatcher, Flux store, observable) as the `source` property and use a [render prop](https://reactjs.org/docs/render-props.html), `children`, to handle the subscribed value when it changes:
|
||||
To use the `Subscription` component, pass the subscribable property (e.g. an event dispatcher, observable) as the `source` property and use a [render prop](https://reactjs.org/docs/render-props.html), `children`, to handle the subscribed value when it changes:
|
||||
|
||||
```js
|
||||
<Subscription source={eventDispatcher}>
|
||||
@@ -137,7 +147,7 @@ Below is an example showing how `create-subscription` can be used with native Pr
|
||||
|
||||
**Note** that it an initial render value of `undefined` is unavoidable due to the fact that Promises provide no way to synchronously read their current value.
|
||||
|
||||
**Note** the lack of a way to "unsubscribe" from a Promise can result in memory leaks as long as something has a reference to the Promise. This should be taken into considerationg when determining whether Promises are appropriate to use in this way within your application.
|
||||
**Note** the lack of a way to "unsubscribe" from a Promise can result in memory leaks as long as something has a reference to the Promise. This should be taken into consideration when determining whether Promises are appropriate to use in this way within your application.
|
||||
|
||||
```js
|
||||
import React from "react";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "create-subscription",
|
||||
"description": "HOC for creating async-safe React components with subscriptions",
|
||||
"version": "0.1.0-alpha.2",
|
||||
"description": "utility for subscribing to external data sources inside React components",
|
||||
"version": "16.3.1",
|
||||
"repository": "facebook/react",
|
||||
"files": [
|
||||
"LICENSE",
|
||||
@@ -13,7 +13,7 @@
|
||||
"fbjs": "^0.8.16"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.3.0-alpha.1 || 16.3.0-alpha.2"
|
||||
"react": "^16.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"rxjs": "^5.5.6"
|
||||
|
||||
@@ -189,6 +189,38 @@ describe('createSubscription', () => {
|
||||
// Ensure that only Promise B causes an update
|
||||
expect(ReactNoop.flush()).toEqual([123]);
|
||||
});
|
||||
|
||||
it('should not call setState for a Promise that resolves after unmount', async () => {
|
||||
const Subscription = createSubscription({
|
||||
getCurrentValue: source => undefined,
|
||||
subscribe: (source, callback) => {
|
||||
source.then(value => callback(value), value => callback(value));
|
||||
// (Can't unsubscribe from a Promise)
|
||||
return () => {};
|
||||
},
|
||||
});
|
||||
|
||||
function render(hasLoaded) {
|
||||
ReactNoop.yield('rendered');
|
||||
return null;
|
||||
}
|
||||
|
||||
let resolvePromise;
|
||||
const promise = new Promise((resolve, reject) => {
|
||||
resolvePromise = resolve;
|
||||
});
|
||||
|
||||
ReactNoop.render(<Subscription source={promise}>{render}</Subscription>);
|
||||
expect(ReactNoop.flush()).toEqual(['rendered']);
|
||||
|
||||
// Unmount
|
||||
ReactNoop.render(null);
|
||||
ReactNoop.flush();
|
||||
|
||||
// Resolve Promise should not trigger a setState warning
|
||||
resolvePromise(true);
|
||||
await promise;
|
||||
});
|
||||
});
|
||||
|
||||
it('should unsubscribe from old subscribables and subscribe to new subscribables when props change', () => {
|
||||
|
||||
@@ -51,9 +51,6 @@ export function createSubscription<Property, Value>(
|
||||
};
|
||||
type State = {
|
||||
source: Property,
|
||||
unsubscribeContainer: {
|
||||
unsubscribe: Unsubscribe | null,
|
||||
},
|
||||
value: Value | void,
|
||||
};
|
||||
|
||||
@@ -61,22 +58,19 @@ export function createSubscription<Property, Value>(
|
||||
class Subscription extends React.Component<Props, State> {
|
||||
state: State = {
|
||||
source: this.props.source,
|
||||
unsubscribeContainer: {
|
||||
unsubscribe: null,
|
||||
},
|
||||
value:
|
||||
this.props.source != null
|
||||
? getCurrentValue(this.props.source)
|
||||
: undefined,
|
||||
};
|
||||
|
||||
_hasUnmounted: boolean = false;
|
||||
_unsubscribe: Unsubscribe | null = null;
|
||||
|
||||
static getDerivedStateFromProps(nextProps, prevState) {
|
||||
if (nextProps.source !== prevState.source) {
|
||||
return {
|
||||
source: nextProps.source,
|
||||
unsubscribeContainer: {
|
||||
unsubscribe: null,
|
||||
},
|
||||
value:
|
||||
nextProps.source != null
|
||||
? getCurrentValue(nextProps.source)
|
||||
@@ -100,6 +94,10 @@ export function createSubscription<Property, Value>(
|
||||
|
||||
componentWillUnmount() {
|
||||
this.unsubscribe(this.state);
|
||||
|
||||
// Track mounted to avoid calling setState after unmounting
|
||||
// For source like Promises that can't be unsubscribed from.
|
||||
this._hasUnmounted = true;
|
||||
}
|
||||
|
||||
render() {
|
||||
@@ -110,6 +108,10 @@ export function createSubscription<Property, Value>(
|
||||
const {source} = this.state;
|
||||
if (source != null) {
|
||||
const callback = (value: Value | void) => {
|
||||
if (this._hasUnmounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState(state => {
|
||||
// If the value is the same, skip the unnecessary state update.
|
||||
if (value === state.value) {
|
||||
@@ -125,18 +127,16 @@ export function createSubscription<Property, Value>(
|
||||
});
|
||||
};
|
||||
|
||||
// Store subscription for later (in case it's needed to unsubscribe).
|
||||
// This is safe to do via mutation since:
|
||||
// 1) It does not impact render.
|
||||
// 2) This method will only be called during the "commit" phase.
|
||||
// Store the unsubscribe method for later (in case the subscribable prop changes).
|
||||
const unsubscribe = subscribe(source, callback);
|
||||
|
||||
invariant(
|
||||
typeof unsubscribe === 'function',
|
||||
'A subscription must return an unsubscribe function.',
|
||||
);
|
||||
|
||||
this.state.unsubscribeContainer.unsubscribe = unsubscribe;
|
||||
// It's safe to store unsubscribe on the instance because
|
||||
// We only read or write that property during the "commit" phase.
|
||||
this._unsubscribe = unsubscribe;
|
||||
|
||||
// External values could change between render and mount,
|
||||
// In some cases it may be important to handle this case.
|
||||
@@ -148,10 +148,10 @@ export function createSubscription<Property, Value>(
|
||||
}
|
||||
|
||||
unsubscribe(state: State) {
|
||||
const {unsubscribe} = state.unsubscribeContainer;
|
||||
if (typeof unsubscribe === 'function') {
|
||||
unsubscribe();
|
||||
if (typeof this._unsubscribe === 'function') {
|
||||
this._unsubscribe();
|
||||
}
|
||||
this._unsubscribe = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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.3.0-alpha.2",
|
||||
"version": "16.3.1",
|
||||
"main": "index.js",
|
||||
"repository": "facebook/react",
|
||||
"keywords": [
|
||||
@@ -26,7 +26,7 @@
|
||||
"prop-types": "^15.6.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.0.0 || 16.3.0-alpha.2"
|
||||
"react": "^16.0.0"
|
||||
},
|
||||
"files": [
|
||||
"LICENSE",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "react-call-return",
|
||||
"description": "Experimental APIs for multi-pass rendering in React.",
|
||||
"version": "0.6.0-alpha.2",
|
||||
"version": "0.7.0",
|
||||
"repository": "facebook/react",
|
||||
"files": [
|
||||
"LICENSE",
|
||||
@@ -14,6 +14,6 @@
|
||||
"object-assign": "^4.1.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.0.0 || 16.3.0-alpha.2"
|
||||
"react": "^16.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "react-dom",
|
||||
"version": "16.3.0-alpha.2",
|
||||
"version": "16.3.1",
|
||||
"description": "React package for working with the DOM.",
|
||||
"main": "index.js",
|
||||
"repository": "facebook/react",
|
||||
@@ -19,7 +19,7 @@
|
||||
"prop-types": "^15.6.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.0.0 || 16.3.0-alpha.2"
|
||||
"react": "^16.0.0"
|
||||
},
|
||||
"files": [
|
||||
"LICENSE",
|
||||
|
||||
@@ -214,10 +214,10 @@ describe('ReactComponentLifeCycle', () => {
|
||||
expect(() => {
|
||||
ReactTestUtils.renderIntoDocument(<StatefulComponent />);
|
||||
}).toWarnDev(
|
||||
'Warning: setState(...): Can only update a mounted or ' +
|
||||
'mounting component. This usually means you called setState() on an ' +
|
||||
'unmounted component. This is a no-op.\n\nPlease check the code for the ' +
|
||||
'StatefulComponent component.',
|
||||
"Warning: Can't call setState on a component that is not yet mounted. " +
|
||||
'This is a no-op, but it might indicate a bug in your application. ' +
|
||||
'Instead, assign to `this.state` directly or define a `state = {};` ' +
|
||||
'class property with the desired state in the StatefulComponent component.',
|
||||
);
|
||||
|
||||
// Check deduplication; (no extra warnings should be logged).
|
||||
@@ -591,6 +591,7 @@ describe('ReactComponentLifeCycle', () => {
|
||||
}
|
||||
componentDidMount = logger('outer componentDidMount');
|
||||
shouldComponentUpdate = logger('outer shouldComponentUpdate');
|
||||
getSnapshotBeforeUpdate = logger('outer getSnapshotBeforeUpdate');
|
||||
componentDidUpdate = logger('outer componentDidUpdate');
|
||||
componentWillUnmount = logger('outer componentWillUnmount');
|
||||
render() {
|
||||
@@ -610,6 +611,7 @@ describe('ReactComponentLifeCycle', () => {
|
||||
}
|
||||
componentDidMount = logger('inner componentDidMount');
|
||||
shouldComponentUpdate = logger('inner shouldComponentUpdate');
|
||||
getSnapshotBeforeUpdate = logger('inner getSnapshotBeforeUpdate');
|
||||
componentDidUpdate = logger('inner componentDidUpdate');
|
||||
componentWillUnmount = logger('inner componentWillUnmount');
|
||||
render() {
|
||||
@@ -635,6 +637,8 @@ describe('ReactComponentLifeCycle', () => {
|
||||
'outer shouldComponentUpdate',
|
||||
'inner getDerivedStateFromProps',
|
||||
'inner shouldComponentUpdate',
|
||||
'inner getSnapshotBeforeUpdate',
|
||||
'outer getSnapshotBeforeUpdate',
|
||||
'inner componentDidUpdate',
|
||||
'outer componentDidUpdate',
|
||||
]);
|
||||
@@ -669,10 +673,38 @@ describe('ReactComponentLifeCycle', () => {
|
||||
|
||||
const container = document.createElement('div');
|
||||
expect(() => ReactDOM.render(<Component />, container)).toWarnDev(
|
||||
'Defines both componentWillReceiveProps',
|
||||
'Unsafe legacy lifecycles will not be called for components using new component APIs.',
|
||||
);
|
||||
});
|
||||
|
||||
it('should not invoke deprecated lifecycles (cWM/cWRP/cWU) if new getSnapshotBeforeUpdate is present', () => {
|
||||
class Component extends React.Component {
|
||||
state = {};
|
||||
getSnapshotBeforeUpdate() {
|
||||
return null;
|
||||
}
|
||||
componentWillMount() {
|
||||
throw Error('unexpected');
|
||||
}
|
||||
componentWillReceiveProps() {
|
||||
throw Error('unexpected');
|
||||
}
|
||||
componentWillUpdate() {
|
||||
throw Error('unexpected');
|
||||
}
|
||||
componentDidUpdate() {}
|
||||
render() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
const container = document.createElement('div');
|
||||
expect(() => ReactDOM.render(<Component value={1} />, container)).toWarnDev(
|
||||
'Unsafe legacy lifecycles will not be called for components using new component APIs.',
|
||||
);
|
||||
ReactDOM.render(<Component value={2} />, container);
|
||||
});
|
||||
|
||||
it('should not invoke new unsafe lifecycles (cWM/cWRP/cWU) if static gDSFP is present', () => {
|
||||
class Component extends React.Component {
|
||||
state = {};
|
||||
@@ -694,8 +726,177 @@ describe('ReactComponentLifeCycle', () => {
|
||||
}
|
||||
|
||||
const container = document.createElement('div');
|
||||
expect(() => ReactDOM.render(<Component />, container)).toWarnDev(
|
||||
'Defines both componentWillReceiveProps',
|
||||
expect(() => ReactDOM.render(<Component value={1} />, container)).toWarnDev(
|
||||
'Unsafe legacy lifecycles will not be called for components using new component APIs.',
|
||||
);
|
||||
ReactDOM.render(<Component value={2} />, container);
|
||||
});
|
||||
|
||||
it('should warn about deprecated lifecycles (cWM/cWRP/cWU) if new static gDSFP is present', () => {
|
||||
const container = document.createElement('div');
|
||||
|
||||
class AllLegacyLifecycles extends React.Component {
|
||||
state = {};
|
||||
static getDerivedStateFromProps() {
|
||||
return null;
|
||||
}
|
||||
componentWillMount() {}
|
||||
UNSAFE_componentWillReceiveProps() {}
|
||||
componentWillUpdate() {}
|
||||
render() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
expect(() => ReactDOM.render(<AllLegacyLifecycles />, container)).toWarnDev(
|
||||
'Unsafe legacy lifecycles will not be called for components using new component APIs.\n\n' +
|
||||
'AllLegacyLifecycles uses getDerivedStateFromProps() but also contains the following legacy lifecycles:\n' +
|
||||
' componentWillMount\n' +
|
||||
' UNSAFE_componentWillReceiveProps\n' +
|
||||
' componentWillUpdate\n\n' +
|
||||
'The above lifecycles should be removed. Learn more about this warning here:\n' +
|
||||
'https://fb.me/react-async-component-lifecycle-hooks',
|
||||
);
|
||||
|
||||
class WillMount extends React.Component {
|
||||
state = {};
|
||||
static getDerivedStateFromProps() {
|
||||
return null;
|
||||
}
|
||||
UNSAFE_componentWillMount() {}
|
||||
render() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
expect(() => ReactDOM.render(<WillMount />, container)).toWarnDev(
|
||||
'Unsafe legacy lifecycles will not be called for components using new component APIs.\n\n' +
|
||||
'WillMount uses getDerivedStateFromProps() but also contains the following legacy lifecycles:\n' +
|
||||
' UNSAFE_componentWillMount\n\n' +
|
||||
'The above lifecycles should be removed. Learn more about this warning here:\n' +
|
||||
'https://fb.me/react-async-component-lifecycle-hooks',
|
||||
);
|
||||
|
||||
class WillMountAndUpdate extends React.Component {
|
||||
state = {};
|
||||
static getDerivedStateFromProps() {
|
||||
return null;
|
||||
}
|
||||
componentWillMount() {}
|
||||
UNSAFE_componentWillUpdate() {}
|
||||
render() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
expect(() => ReactDOM.render(<WillMountAndUpdate />, container)).toWarnDev(
|
||||
'Unsafe legacy lifecycles will not be called for components using new component APIs.\n\n' +
|
||||
'WillMountAndUpdate uses getDerivedStateFromProps() but also contains the following legacy lifecycles:\n' +
|
||||
' componentWillMount\n' +
|
||||
' UNSAFE_componentWillUpdate\n\n' +
|
||||
'The above lifecycles should be removed. Learn more about this warning here:\n' +
|
||||
'https://fb.me/react-async-component-lifecycle-hooks',
|
||||
);
|
||||
|
||||
class WillReceiveProps extends React.Component {
|
||||
state = {};
|
||||
static getDerivedStateFromProps() {
|
||||
return null;
|
||||
}
|
||||
componentWillReceiveProps() {}
|
||||
render() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
expect(() => ReactDOM.render(<WillReceiveProps />, container)).toWarnDev(
|
||||
'Unsafe legacy lifecycles will not be called for components using new component APIs.\n\n' +
|
||||
'WillReceiveProps uses getDerivedStateFromProps() but also contains the following legacy lifecycles:\n' +
|
||||
' componentWillReceiveProps\n\n' +
|
||||
'The above lifecycles should be removed. Learn more about this warning here:\n' +
|
||||
'https://fb.me/react-async-component-lifecycle-hooks',
|
||||
);
|
||||
});
|
||||
|
||||
it('should warn about deprecated lifecycles (cWM/cWRP/cWU) if new getSnapshotBeforeUpdate is present', () => {
|
||||
const container = document.createElement('div');
|
||||
|
||||
class AllLegacyLifecycles extends React.Component {
|
||||
state = {};
|
||||
getSnapshotBeforeUpdate() {}
|
||||
componentWillMount() {}
|
||||
UNSAFE_componentWillReceiveProps() {}
|
||||
componentWillUpdate() {}
|
||||
componentDidUpdate() {}
|
||||
render() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
expect(() => ReactDOM.render(<AllLegacyLifecycles />, container)).toWarnDev(
|
||||
'Unsafe legacy lifecycles will not be called for components using new component APIs.\n\n' +
|
||||
'AllLegacyLifecycles uses getSnapshotBeforeUpdate() but also contains the following legacy lifecycles:\n' +
|
||||
' componentWillMount\n' +
|
||||
' UNSAFE_componentWillReceiveProps\n' +
|
||||
' componentWillUpdate\n\n' +
|
||||
'The above lifecycles should be removed. Learn more about this warning here:\n' +
|
||||
'https://fb.me/react-async-component-lifecycle-hooks',
|
||||
);
|
||||
|
||||
class WillMount extends React.Component {
|
||||
state = {};
|
||||
getSnapshotBeforeUpdate() {}
|
||||
UNSAFE_componentWillMount() {}
|
||||
componentDidUpdate() {}
|
||||
render() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
expect(() => ReactDOM.render(<WillMount />, container)).toWarnDev(
|
||||
'Unsafe legacy lifecycles will not be called for components using new component APIs.\n\n' +
|
||||
'WillMount uses getSnapshotBeforeUpdate() but also contains the following legacy lifecycles:\n' +
|
||||
' UNSAFE_componentWillMount\n\n' +
|
||||
'The above lifecycles should be removed. Learn more about this warning here:\n' +
|
||||
'https://fb.me/react-async-component-lifecycle-hooks',
|
||||
);
|
||||
|
||||
class WillMountAndUpdate extends React.Component {
|
||||
state = {};
|
||||
getSnapshotBeforeUpdate() {}
|
||||
componentWillMount() {}
|
||||
UNSAFE_componentWillUpdate() {}
|
||||
componentDidUpdate() {}
|
||||
render() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
expect(() => ReactDOM.render(<WillMountAndUpdate />, container)).toWarnDev(
|
||||
'Unsafe legacy lifecycles will not be called for components using new component APIs.\n\n' +
|
||||
'WillMountAndUpdate uses getSnapshotBeforeUpdate() but also contains the following legacy lifecycles:\n' +
|
||||
' componentWillMount\n' +
|
||||
' UNSAFE_componentWillUpdate\n\n' +
|
||||
'The above lifecycles should be removed. Learn more about this warning here:\n' +
|
||||
'https://fb.me/react-async-component-lifecycle-hooks',
|
||||
);
|
||||
|
||||
class WillReceiveProps extends React.Component {
|
||||
state = {};
|
||||
getSnapshotBeforeUpdate() {}
|
||||
componentWillReceiveProps() {}
|
||||
componentDidUpdate() {}
|
||||
render() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
expect(() => ReactDOM.render(<WillReceiveProps />, container)).toWarnDev(
|
||||
'Unsafe legacy lifecycles will not be called for components using new component APIs.\n\n' +
|
||||
'WillReceiveProps uses getSnapshotBeforeUpdate() but also contains the following legacy lifecycles:\n' +
|
||||
' componentWillReceiveProps\n\n' +
|
||||
'The above lifecycles should be removed. Learn more about this warning here:\n' +
|
||||
'https://fb.me/react-async-component-lifecycle-hooks',
|
||||
);
|
||||
});
|
||||
|
||||
@@ -830,4 +1031,201 @@ describe('ReactComponentLifeCycle', () => {
|
||||
'UNSAFE_componentWillUpdate',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should not override state with stale values if prevState is spread within getDerivedStateFromProps', () => {
|
||||
const divRef = React.createRef();
|
||||
let childInstance;
|
||||
|
||||
class Child extends React.Component {
|
||||
state = {local: 0};
|
||||
static getDerivedStateFromProps(nextProps, prevState) {
|
||||
return {...prevState, remote: nextProps.remote};
|
||||
}
|
||||
updateState = () => {
|
||||
this.setState(state => ({local: state.local + 1}));
|
||||
this.props.onChange(this.state.remote + 1);
|
||||
};
|
||||
render() {
|
||||
childInstance = this;
|
||||
return (
|
||||
<div onClick={this.updateState} ref={divRef}>{`remote:${
|
||||
this.state.remote
|
||||
}, local:${this.state.local}`}</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class Parent extends React.Component {
|
||||
state = {value: 0};
|
||||
handleChange = value => {
|
||||
this.setState({value});
|
||||
};
|
||||
render() {
|
||||
return <Child remote={this.state.value} onChange={this.handleChange} />;
|
||||
}
|
||||
}
|
||||
|
||||
ReactTestUtils.renderIntoDocument(<Parent />);
|
||||
expect(divRef.current.textContent).toBe('remote:0, local:0');
|
||||
|
||||
// Trigger setState() calls
|
||||
childInstance.updateState();
|
||||
expect(divRef.current.textContent).toBe('remote:1, local:1');
|
||||
|
||||
// Trigger batched setState() calls
|
||||
ReactTestUtils.Simulate.click(divRef.current);
|
||||
expect(divRef.current.textContent).toBe('remote:2, local:2');
|
||||
});
|
||||
|
||||
it('should pass the return value from getSnapshotBeforeUpdate to componentDidUpdate', () => {
|
||||
const log = [];
|
||||
|
||||
class MyComponent extends React.Component {
|
||||
state = {
|
||||
value: 0,
|
||||
};
|
||||
static getDerivedStateFromProps(nextProps, prevState) {
|
||||
return {
|
||||
value: prevState.value + 1,
|
||||
};
|
||||
}
|
||||
getSnapshotBeforeUpdate(prevProps, prevState) {
|
||||
log.push(
|
||||
`getSnapshotBeforeUpdate() prevProps:${prevProps.value} prevState:${
|
||||
prevState.value
|
||||
}`,
|
||||
);
|
||||
return 'abc';
|
||||
}
|
||||
componentDidUpdate(prevProps, prevState, snapshot) {
|
||||
log.push(
|
||||
`componentDidUpdate() prevProps:${prevProps.value} prevState:${
|
||||
prevState.value
|
||||
} snapshot:${snapshot}`,
|
||||
);
|
||||
}
|
||||
render() {
|
||||
log.push('render');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
const div = document.createElement('div');
|
||||
ReactDOM.render(
|
||||
<div>
|
||||
<MyComponent value="foo" />
|
||||
</div>,
|
||||
div,
|
||||
);
|
||||
expect(log).toEqual(['render']);
|
||||
log.length = 0;
|
||||
|
||||
ReactDOM.render(
|
||||
<div>
|
||||
<MyComponent value="bar" />
|
||||
</div>,
|
||||
div,
|
||||
);
|
||||
expect(log).toEqual([
|
||||
'render',
|
||||
'getSnapshotBeforeUpdate() prevProps:foo prevState:1',
|
||||
'componentDidUpdate() prevProps:foo prevState:1 snapshot:abc',
|
||||
]);
|
||||
log.length = 0;
|
||||
|
||||
ReactDOM.render(
|
||||
<div>
|
||||
<MyComponent value="baz" />
|
||||
</div>,
|
||||
div,
|
||||
);
|
||||
expect(log).toEqual([
|
||||
'render',
|
||||
'getSnapshotBeforeUpdate() prevProps:bar prevState:2',
|
||||
'componentDidUpdate() prevProps:bar prevState:2 snapshot:abc',
|
||||
]);
|
||||
log.length = 0;
|
||||
|
||||
ReactDOM.render(<div />, div);
|
||||
expect(log).toEqual([]);
|
||||
});
|
||||
|
||||
it('should call getSnapshotBeforeUpdate before mutations are committed', () => {
|
||||
const log = [];
|
||||
|
||||
class MyComponent extends React.Component {
|
||||
divRef = React.createRef();
|
||||
getSnapshotBeforeUpdate(prevProps, prevState) {
|
||||
log.push('getSnapshotBeforeUpdate');
|
||||
expect(this.divRef.current.textContent).toBe(
|
||||
`value:${prevProps.value}`,
|
||||
);
|
||||
return 'foobar';
|
||||
}
|
||||
componentDidUpdate(prevProps, prevState, snapshot) {
|
||||
log.push('componentDidUpdate');
|
||||
expect(this.divRef.current.textContent).toBe(
|
||||
`value:${this.props.value}`,
|
||||
);
|
||||
expect(snapshot).toBe('foobar');
|
||||
}
|
||||
render() {
|
||||
log.push('render');
|
||||
return <div ref={this.divRef}>{`value:${this.props.value}`}</div>;
|
||||
}
|
||||
}
|
||||
|
||||
const div = document.createElement('div');
|
||||
ReactDOM.render(<MyComponent value="foo" />, div);
|
||||
expect(log).toEqual(['render']);
|
||||
log.length = 0;
|
||||
|
||||
ReactDOM.render(<MyComponent value="bar" />, div);
|
||||
expect(log).toEqual([
|
||||
'render',
|
||||
'getSnapshotBeforeUpdate',
|
||||
'componentDidUpdate',
|
||||
]);
|
||||
log.length = 0;
|
||||
});
|
||||
|
||||
it('should warn if getSnapshotBeforeUpdate returns undefined', () => {
|
||||
class MyComponent extends React.Component {
|
||||
getSnapshotBeforeUpdate() {}
|
||||
componentDidUpdate() {}
|
||||
render() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
const div = document.createElement('div');
|
||||
ReactDOM.render(<MyComponent value="foo" />, div);
|
||||
expect(() => ReactDOM.render(<MyComponent value="bar" />, div)).toWarnDev(
|
||||
'MyComponent.getSnapshotBeforeUpdate(): A snapshot value (or null) must ' +
|
||||
'be returned. You have returned undefined.',
|
||||
);
|
||||
|
||||
// De-duped
|
||||
ReactDOM.render(<MyComponent value="baz" />, div);
|
||||
});
|
||||
|
||||
it('should warn if getSnapshotBeforeUpdate is defined with no componentDidUpdate', () => {
|
||||
class MyComponent extends React.Component {
|
||||
getSnapshotBeforeUpdate() {
|
||||
return null;
|
||||
}
|
||||
render() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
const div = document.createElement('div');
|
||||
expect(() => ReactDOM.render(<MyComponent />, div)).toWarnDev(
|
||||
'MyComponent: getSnapshotBeforeUpdate() should be used with componentDidUpdate(). ' +
|
||||
'This component defines getSnapshotBeforeUpdate() only.',
|
||||
);
|
||||
|
||||
// De-duped
|
||||
ReactDOM.render(<MyComponent />, div);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -225,6 +225,54 @@ describe('ReactCompositeComponent', () => {
|
||||
expect(inputProps.prop).not.toBeDefined();
|
||||
});
|
||||
|
||||
it('should warn about `forceUpdate` on not-yet-mounted components', () => {
|
||||
class MyComponent extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.forceUpdate();
|
||||
}
|
||||
render() {
|
||||
return <div />;
|
||||
}
|
||||
}
|
||||
|
||||
const container = document.createElement('div');
|
||||
expect(() => ReactDOM.render(<MyComponent />, container)).toWarnDev(
|
||||
"Warning: Can't call forceUpdate on a component that is not yet mounted. " +
|
||||
'This is a no-op, but it might indicate a bug in your application. ' +
|
||||
'Instead, assign to `this.state` directly or define a `state = {};` ' +
|
||||
'class property with the desired state in the MyComponent component.',
|
||||
);
|
||||
|
||||
// No additional warning should be recorded
|
||||
const container2 = document.createElement('div');
|
||||
ReactDOM.render(<MyComponent />, container2);
|
||||
});
|
||||
|
||||
it('should warn about `setState` on not-yet-mounted components', () => {
|
||||
class MyComponent extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.setState();
|
||||
}
|
||||
render() {
|
||||
return <div />;
|
||||
}
|
||||
}
|
||||
|
||||
const container = document.createElement('div');
|
||||
expect(() => ReactDOM.render(<MyComponent />, container)).toWarnDev(
|
||||
"Warning: Can't call setState on a component that is not yet mounted. " +
|
||||
'This is a no-op, but it might indicate a bug in your application. ' +
|
||||
'Instead, assign to `this.state` directly or define a `state = {};` ' +
|
||||
'class property with the desired state in the MyComponent component.',
|
||||
);
|
||||
|
||||
// No additional warning should be recorded
|
||||
const container2 = document.createElement('div');
|
||||
ReactDOM.render(<MyComponent />, container2);
|
||||
});
|
||||
|
||||
it('should warn about `forceUpdate` on unmounted components', () => {
|
||||
const container = document.createElement('div');
|
||||
document.body.appendChild(container);
|
||||
@@ -244,10 +292,11 @@ describe('ReactCompositeComponent', () => {
|
||||
ReactDOM.unmountComponentAtNode(container);
|
||||
|
||||
expect(() => instance.forceUpdate()).toWarnDev(
|
||||
'Can only update a mounted or mounting component. This usually means ' +
|
||||
'you called setState, replaceState, or forceUpdate on an unmounted ' +
|
||||
'component. This is a no-op.\n\nPlease check the code for the ' +
|
||||
'Component component.',
|
||||
"Warning: Can't call setState (or forceUpdate) on an unmounted " +
|
||||
'component. This is a no-op, but it indicates a memory leak in your ' +
|
||||
'application. To fix, cancel all subscriptions and asynchronous ' +
|
||||
'tasks in the componentWillUnmount method.\n' +
|
||||
' in Component (at **)',
|
||||
);
|
||||
|
||||
// No additional warning should be recorded
|
||||
@@ -269,10 +318,15 @@ describe('ReactCompositeComponent', () => {
|
||||
}
|
||||
}
|
||||
|
||||
let instance = <Component />;
|
||||
expect(instance.setState).not.toBeDefined();
|
||||
|
||||
instance = ReactDOM.render(instance, container);
|
||||
let instance;
|
||||
ReactDOM.render(
|
||||
<div>
|
||||
<span>
|
||||
<Component ref={c => (instance = c || instance)} />
|
||||
</span>
|
||||
</div>,
|
||||
container,
|
||||
);
|
||||
|
||||
expect(renders).toBe(1);
|
||||
|
||||
@@ -280,15 +334,17 @@ describe('ReactCompositeComponent', () => {
|
||||
|
||||
expect(renders).toBe(2);
|
||||
|
||||
ReactDOM.unmountComponentAtNode(container);
|
||||
ReactDOM.render(<div />, container);
|
||||
|
||||
expect(() => {
|
||||
instance.setState({value: 2});
|
||||
}).toWarnDev(
|
||||
'Can only update a mounted or mounting component. This usually means ' +
|
||||
'you called setState, replaceState, or forceUpdate on an unmounted ' +
|
||||
'component. This is a no-op.\n\nPlease check the code for the ' +
|
||||
'Component component.',
|
||||
"Warning: Can't call setState (or forceUpdate) on an unmounted " +
|
||||
'component. This is a no-op, but it indicates a memory leak in your ' +
|
||||
'application. To fix, cancel all subscriptions and asynchronous ' +
|
||||
'tasks in the componentWillUnmount method.\n' +
|
||||
' in Component (at **)\n' +
|
||||
' in span',
|
||||
);
|
||||
|
||||
expect(renders).toBe(2);
|
||||
|
||||
@@ -68,12 +68,11 @@ describe('ReactDOMFiberAsync', () => {
|
||||
ReactFeatureFlags = require('shared/ReactFeatureFlags');
|
||||
container = document.createElement('div');
|
||||
ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false;
|
||||
ReactFeatureFlags.enableCreateRoot = true;
|
||||
ReactDOM = require('react-dom');
|
||||
});
|
||||
|
||||
it('createRoot makes the entire tree async', () => {
|
||||
const root = ReactDOM.createRoot(container);
|
||||
const root = ReactDOM.unstable_createRoot(container);
|
||||
root.render(<div>Hi</div>);
|
||||
expect(container.textContent).toEqual('');
|
||||
jest.runAllTimers();
|
||||
@@ -95,7 +94,7 @@ describe('ReactDOMFiberAsync', () => {
|
||||
}
|
||||
}
|
||||
|
||||
const root = ReactDOM.createRoot(container);
|
||||
const root = ReactDOM.unstable_createRoot(container);
|
||||
root.render(<Component />);
|
||||
expect(container.textContent).toEqual('');
|
||||
jest.runAllTimers();
|
||||
|
||||
@@ -66,7 +66,6 @@ describe('ReactDOMRoot', () => {
|
||||
};
|
||||
|
||||
jest.resetModules();
|
||||
require('shared/ReactFeatureFlags').enableCreateRoot = true;
|
||||
React = require('react');
|
||||
ReactDOM = require('react-dom');
|
||||
ReactDOMServer = require('react-dom/server');
|
||||
@@ -74,14 +73,14 @@ describe('ReactDOMRoot', () => {
|
||||
});
|
||||
|
||||
it('renders children', () => {
|
||||
const root = ReactDOM.createRoot(container);
|
||||
const root = ReactDOM.unstable_createRoot(container);
|
||||
root.render(<div>Hi</div>);
|
||||
flush();
|
||||
expect(container.textContent).toEqual('Hi');
|
||||
});
|
||||
|
||||
it('unmounts children', () => {
|
||||
const root = ReactDOM.createRoot(container);
|
||||
const root = ReactDOM.unstable_createRoot(container);
|
||||
root.render(<div>Hi</div>);
|
||||
flush();
|
||||
expect(container.textContent).toEqual('Hi');
|
||||
@@ -91,7 +90,7 @@ describe('ReactDOMRoot', () => {
|
||||
});
|
||||
|
||||
it('`root.render` returns a thenable work object', () => {
|
||||
const root = ReactDOM.createRoot(container);
|
||||
const root = ReactDOM.unstable_createRoot(container);
|
||||
const work = root.render(<AsyncMode>Hi</AsyncMode>);
|
||||
let ops = [];
|
||||
work.then(() => {
|
||||
@@ -109,7 +108,7 @@ describe('ReactDOMRoot', () => {
|
||||
});
|
||||
|
||||
it('resolves `work.then` callback synchronously if the work already committed', () => {
|
||||
const root = ReactDOM.createRoot(container);
|
||||
const root = ReactDOM.unstable_createRoot(container);
|
||||
const work = root.render(<AsyncMode>Hi</AsyncMode>);
|
||||
flush();
|
||||
let ops = [];
|
||||
@@ -133,7 +132,7 @@ describe('ReactDOMRoot', () => {
|
||||
// Does not hydrate by default
|
||||
const container1 = document.createElement('div');
|
||||
container1.innerHTML = markup;
|
||||
const root1 = ReactDOM.createRoot(container1);
|
||||
const root1 = ReactDOM.unstable_createRoot(container1);
|
||||
root1.render(
|
||||
<div>
|
||||
<span />
|
||||
@@ -144,7 +143,7 @@ describe('ReactDOMRoot', () => {
|
||||
// Accepts `hydrate` option
|
||||
const container2 = document.createElement('div');
|
||||
container2.innerHTML = markup;
|
||||
const root2 = ReactDOM.createRoot(container2, {hydrate: true});
|
||||
const root2 = ReactDOM.unstable_createRoot(container2, {hydrate: true});
|
||||
root2.render(
|
||||
<div>
|
||||
<span />
|
||||
@@ -155,7 +154,7 @@ describe('ReactDOMRoot', () => {
|
||||
|
||||
it('does not clear existing children', async () => {
|
||||
container.innerHTML = '<div>a</div><div>b</div>';
|
||||
const root = ReactDOM.createRoot(container);
|
||||
const root = ReactDOM.unstable_createRoot(container);
|
||||
root.render(
|
||||
<div>
|
||||
<span>c</span>
|
||||
@@ -175,7 +174,7 @@ describe('ReactDOMRoot', () => {
|
||||
});
|
||||
|
||||
it('can defer a commit by batching it', () => {
|
||||
const root = ReactDOM.createRoot(container);
|
||||
const root = ReactDOM.unstable_createRoot(container);
|
||||
const batch = root.createBatch();
|
||||
batch.render(<div>Hi</div>);
|
||||
// Hasn't committed yet
|
||||
@@ -185,13 +184,53 @@ describe('ReactDOMRoot', () => {
|
||||
expect(container.textContent).toEqual('Hi');
|
||||
});
|
||||
|
||||
it('applies setState in componentDidMount synchronously in a batch', done => {
|
||||
class App extends React.Component {
|
||||
state = {mounted: false};
|
||||
componentDidMount() {
|
||||
this.setState({
|
||||
mounted: true,
|
||||
});
|
||||
}
|
||||
render() {
|
||||
return this.state.mounted ? 'Hi' : 'Bye';
|
||||
}
|
||||
}
|
||||
|
||||
const root = ReactDOM.unstable_createRoot(container);
|
||||
const batch = root.createBatch();
|
||||
batch.render(
|
||||
<AsyncMode>
|
||||
<App />
|
||||
</AsyncMode>,
|
||||
);
|
||||
|
||||
flush();
|
||||
|
||||
// Hasn't updated yet
|
||||
expect(container.textContent).toEqual('');
|
||||
|
||||
let ops = [];
|
||||
batch.then(() => {
|
||||
// Still hasn't updated
|
||||
ops.push(container.textContent);
|
||||
|
||||
// Should synchronously commit
|
||||
batch.commit();
|
||||
ops.push(container.textContent);
|
||||
|
||||
expect(ops).toEqual(['', 'Hi']);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('does not restart a completed batch when committing if there were no intervening updates', () => {
|
||||
let ops = [];
|
||||
function Foo(props) {
|
||||
ops.push('Foo');
|
||||
return props.children;
|
||||
}
|
||||
const root = ReactDOM.createRoot(container);
|
||||
const root = ReactDOM.unstable_createRoot(container);
|
||||
const batch = root.createBatch();
|
||||
batch.render(<Foo>Hi</Foo>);
|
||||
// Flush all async work.
|
||||
@@ -209,7 +248,7 @@ describe('ReactDOMRoot', () => {
|
||||
});
|
||||
|
||||
it('can wait for a batch to finish', () => {
|
||||
const root = ReactDOM.createRoot(container);
|
||||
const root = ReactDOM.unstable_createRoot(container);
|
||||
const batch = root.createBatch();
|
||||
batch.render(<AsyncMode>Foo</AsyncMode>);
|
||||
|
||||
@@ -231,7 +270,7 @@ describe('ReactDOMRoot', () => {
|
||||
});
|
||||
|
||||
it('`batch.render` returns a thenable work object', () => {
|
||||
const root = ReactDOM.createRoot(container);
|
||||
const root = ReactDOM.unstable_createRoot(container);
|
||||
const batch = root.createBatch();
|
||||
const work = batch.render('Hi');
|
||||
let ops = [];
|
||||
@@ -250,7 +289,7 @@ describe('ReactDOMRoot', () => {
|
||||
});
|
||||
|
||||
it('can commit an empty batch', () => {
|
||||
const root = ReactDOM.createRoot(container);
|
||||
const root = ReactDOM.unstable_createRoot(container);
|
||||
root.render(<AsyncMode>1</AsyncMode>);
|
||||
|
||||
expire(2000);
|
||||
@@ -267,7 +306,7 @@ describe('ReactDOMRoot', () => {
|
||||
|
||||
it('two batches created simultaneously are committed separately', () => {
|
||||
// (In other words, they have distinct expiration times)
|
||||
const root = ReactDOM.createRoot(container);
|
||||
const root = ReactDOM.unstable_createRoot(container);
|
||||
const batch1 = root.createBatch();
|
||||
batch1.render(1);
|
||||
const batch2 = root.createBatch();
|
||||
@@ -283,7 +322,7 @@ describe('ReactDOMRoot', () => {
|
||||
});
|
||||
|
||||
it('commits an earlier batch without committing a later batch', () => {
|
||||
const root = ReactDOM.createRoot(container);
|
||||
const root = ReactDOM.unstable_createRoot(container);
|
||||
const batch1 = root.createBatch();
|
||||
batch1.render(1);
|
||||
|
||||
@@ -302,7 +341,7 @@ describe('ReactDOMRoot', () => {
|
||||
});
|
||||
|
||||
it('commits a later batch without committing an earlier batch', () => {
|
||||
const root = ReactDOM.createRoot(container);
|
||||
const root = ReactDOM.unstable_createRoot(container);
|
||||
const batch1 = root.createBatch();
|
||||
batch1.render(1);
|
||||
|
||||
@@ -320,4 +359,14 @@ describe('ReactDOMRoot', () => {
|
||||
flush();
|
||||
expect(container.textContent).toEqual('1');
|
||||
});
|
||||
|
||||
it('handles fatal errors triggered by batch.commit()', () => {
|
||||
const root = ReactDOM.unstable_createRoot(container);
|
||||
const batch = root.createBatch();
|
||||
const InvalidType = undefined;
|
||||
expect(() => batch.render(<InvalidType />)).toWarnDev([
|
||||
'React.createElement: type is invalid',
|
||||
]);
|
||||
expect(() => batch.commit()).toThrow('Element type is invalid');
|
||||
});
|
||||
});
|
||||
@@ -2081,4 +2081,44 @@ describe('ReactErrorBoundaries', () => {
|
||||
});
|
||||
}).toThrow('foo error');
|
||||
});
|
||||
|
||||
it('handles errors that occur in before-mutation commit hook', () => {
|
||||
const errors = [];
|
||||
let caughtError;
|
||||
class Parent extends React.Component {
|
||||
getSnapshotBeforeUpdate() {
|
||||
errors.push('parent sad');
|
||||
throw new Error('parent sad');
|
||||
}
|
||||
componentDidUpdate() {}
|
||||
render() {
|
||||
return <Child {...this.props} />;
|
||||
}
|
||||
}
|
||||
class Child extends React.Component {
|
||||
getSnapshotBeforeUpdate() {
|
||||
errors.push('child sad');
|
||||
throw new Error('child sad');
|
||||
}
|
||||
componentDidUpdate() {}
|
||||
render() {
|
||||
return <div />;
|
||||
}
|
||||
}
|
||||
|
||||
const container = document.createElement('div');
|
||||
ReactDOM.render(<Parent value={1} />, container);
|
||||
try {
|
||||
ReactDOM.render(<Parent value={2} />, container);
|
||||
} catch (e) {
|
||||
if (e.message !== 'parent sad' && e.message !== 'child sad') {
|
||||
throw e;
|
||||
}
|
||||
caughtError = e;
|
||||
}
|
||||
|
||||
expect(errors).toEqual(['child sad', 'parent sad']);
|
||||
// Error should be the first thrown
|
||||
expect(caughtError.message).toBe('child sad');
|
||||
});
|
||||
});
|
||||
|
||||
19
packages/react-dom/src/client/ReactDOM.js
vendored
19
packages/react-dom/src/client/ReactDOM.js
vendored
@@ -28,7 +28,6 @@ import * as EventPluginHub from 'events/EventPluginHub';
|
||||
import * as EventPluginRegistry from 'events/EventPluginRegistry';
|
||||
import * as EventPropagators from 'events/EventPropagators';
|
||||
import * as ReactInstanceMap from 'shared/ReactInstanceMap';
|
||||
import {enableCreateRoot} from 'shared/ReactFeatureFlags';
|
||||
import ReactVersion from 'shared/ReactVersion';
|
||||
import * as ReactDOMFrameScheduling from 'shared/ReactDOMFrameScheduling';
|
||||
import {ReactCurrentOwner} from 'shared/ReactGlobalSharedState';
|
||||
@@ -1126,7 +1125,7 @@ const ReactDOM: Object = {
|
||||
): null | Element | Text {
|
||||
if (__DEV__) {
|
||||
let owner = (ReactCurrentOwner.current: any);
|
||||
if (owner !== null) {
|
||||
if (owner !== null && owner.stateNode !== null) {
|
||||
const warnedAboutRefsInRender =
|
||||
owner.stateNode._warnedAboutRefsInRender;
|
||||
warning(
|
||||
@@ -1304,15 +1303,13 @@ type RootOptions = {
|
||||
hydrate?: boolean,
|
||||
};
|
||||
|
||||
if (enableCreateRoot) {
|
||||
ReactDOM.createRoot = function createRoot(
|
||||
container: DOMContainer,
|
||||
options?: RootOptions,
|
||||
): ReactRoot {
|
||||
const hydrate = options != null && options.hydrate === true;
|
||||
return new ReactRoot(container, true, hydrate);
|
||||
};
|
||||
}
|
||||
ReactDOM.unstable_createRoot = function createRoot(
|
||||
container: DOMContainer,
|
||||
options?: RootOptions,
|
||||
): ReactRoot {
|
||||
const hydrate = options != null && options.hydrate === true;
|
||||
return new ReactRoot(container, true, hydrate);
|
||||
};
|
||||
|
||||
const foundDevTools = DOMRenderer.injectIntoDevTools({
|
||||
findFiberByHostInstance: ReactDOMComponentTree.getClosestInstanceFromNode,
|
||||
|
||||
@@ -43,9 +43,7 @@ describe('BeforeInputEventPlugin', () => {
|
||||
|
||||
function simulateEvent(elem, type, data) {
|
||||
const event = new Event(type, {bubbles: true});
|
||||
Object.keys(data).forEach(key => {
|
||||
event[key] = data[key];
|
||||
});
|
||||
Object.assign(event, data);
|
||||
elem.dispatchEvent(event);
|
||||
}
|
||||
|
||||
|
||||
@@ -452,12 +452,11 @@ describe('ChangeEventPlugin', () => {
|
||||
ReactFeatureFlags = require('shared/ReactFeatureFlags');
|
||||
ReactFeatureFlags.enableAsyncSubtreeAPI = true;
|
||||
ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false;
|
||||
ReactFeatureFlags.enableCreateRoot = true;
|
||||
ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false;
|
||||
ReactDOM = require('react-dom');
|
||||
});
|
||||
it('text input', () => {
|
||||
const root = ReactDOM.createRoot(container);
|
||||
const root = ReactDOM.unstable_createRoot(container);
|
||||
let input;
|
||||
|
||||
let ops = [];
|
||||
@@ -504,7 +503,7 @@ describe('ChangeEventPlugin', () => {
|
||||
});
|
||||
|
||||
it('checkbox input', () => {
|
||||
const root = ReactDOM.createRoot(container);
|
||||
const root = ReactDOM.unstable_createRoot(container);
|
||||
let input;
|
||||
|
||||
let ops = [];
|
||||
@@ -566,7 +565,7 @@ describe('ChangeEventPlugin', () => {
|
||||
});
|
||||
|
||||
it('textarea', () => {
|
||||
const root = ReactDOM.createRoot(container);
|
||||
const root = ReactDOM.unstable_createRoot(container);
|
||||
let textarea;
|
||||
|
||||
let ops = [];
|
||||
@@ -613,7 +612,7 @@ describe('ChangeEventPlugin', () => {
|
||||
});
|
||||
|
||||
it('parent of input', () => {
|
||||
const root = ReactDOM.createRoot(container);
|
||||
const root = ReactDOM.unstable_createRoot(container);
|
||||
let input;
|
||||
|
||||
let ops = [];
|
||||
@@ -664,7 +663,7 @@ describe('ChangeEventPlugin', () => {
|
||||
});
|
||||
|
||||
it('is async for non-input events', () => {
|
||||
const root = ReactDOM.createRoot(container);
|
||||
const root = ReactDOM.unstable_createRoot(container);
|
||||
let input;
|
||||
|
||||
let ops = [];
|
||||
|
||||
@@ -223,13 +223,12 @@ describe('SimpleEventPlugin', function() {
|
||||
ReactFeatureFlags = require('shared/ReactFeatureFlags');
|
||||
ReactFeatureFlags.enableAsyncSubtreeAPI = true;
|
||||
ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false;
|
||||
ReactFeatureFlags.enableCreateRoot = true;
|
||||
ReactDOM = require('react-dom');
|
||||
});
|
||||
|
||||
it('flushes pending interactive work before extracting event handler', () => {
|
||||
const container = document.createElement('div');
|
||||
const root = ReactDOM.createRoot(container);
|
||||
const root = ReactDOM.unstable_createRoot(container);
|
||||
document.body.appendChild(container);
|
||||
|
||||
let ops = [];
|
||||
@@ -309,7 +308,7 @@ describe('SimpleEventPlugin', function() {
|
||||
|
||||
it('end result of many interactive updates is deterministic', () => {
|
||||
const container = document.createElement('div');
|
||||
const root = ReactDOM.createRoot(container);
|
||||
const root = ReactDOM.unstable_createRoot(container);
|
||||
document.body.appendChild(container);
|
||||
|
||||
let button;
|
||||
|
||||
@@ -675,7 +675,7 @@ class ReactDOMServerRenderer {
|
||||
pushProvider<T>(provider: ReactProvider<T>): void {
|
||||
this.providerIndex += 1;
|
||||
this.providerStack[this.providerIndex] = provider;
|
||||
const context: ReactContext<any> = provider.type.context;
|
||||
const context: ReactContext<any> = provider.type._context;
|
||||
context._currentValue = provider.props.value;
|
||||
}
|
||||
|
||||
@@ -689,7 +689,7 @@ class ReactDOMServerRenderer {
|
||||
}
|
||||
this.providerStack[this.providerIndex] = null;
|
||||
this.providerIndex -= 1;
|
||||
const context: ReactContext<any> = provider.type.context;
|
||||
const context: ReactContext<any> = provider.type._context;
|
||||
if (this.providerIndex < 0) {
|
||||
context._currentValue = context._defaultValue;
|
||||
} else {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# `react-is`
|
||||
|
||||
This package allows you to test arbitrary values and see if they're a particular React type, e.g. React Elements.
|
||||
This package allows you to test arbitrary values and see if they're a particular React element type.
|
||||
|
||||
## Installation
|
||||
|
||||
@@ -14,7 +14,37 @@ npm install react-is --save
|
||||
|
||||
## Usage
|
||||
|
||||
### AsyncMode
|
||||
### Determining if a Component is Valid
|
||||
|
||||
```js
|
||||
import * as ReactIs from "react-is";
|
||||
|
||||
class ClassComponent extends React.Component {
|
||||
render() {
|
||||
return React.createElement("div");
|
||||
}
|
||||
}
|
||||
|
||||
const StatelessComponent = () => React.createElement("div");
|
||||
|
||||
const ForwardRefComponent = React.forwardRef((props, ref) =>
|
||||
React.createElement(Component, { forwardedRef: ref, ...props })
|
||||
);
|
||||
|
||||
const Context = React.createContext(false);
|
||||
|
||||
ReactIs.isValidElementType("div"); // true
|
||||
ReactIs.isValidElementType(ClassComponent); // true
|
||||
ReactIs.isValidElementType(StatelessComponent); // true
|
||||
ReactIs.isValidElementType(ForwardRefComponent); // true
|
||||
ReactIs.isValidElementType(Context.Provider); // true
|
||||
ReactIs.isValidElementType(Context.Consumer); // true
|
||||
ReactIs.isValidElementType(React.createFactory("div")); // true
|
||||
```
|
||||
|
||||
### Determining an Element's Type
|
||||
|
||||
#### AsyncMode
|
||||
|
||||
```js
|
||||
import React from "react";
|
||||
@@ -24,7 +54,7 @@ ReactIs.isAsyncMode(<React.unstable_AsyncMode />); // true
|
||||
ReactIs.typeOf(<React.unstable_AsyncMode />) === ReactIs.AsyncMode; // true
|
||||
```
|
||||
|
||||
### Context
|
||||
#### Context
|
||||
|
||||
```js
|
||||
import React from "react";
|
||||
@@ -38,7 +68,7 @@ ReactIs.typeOf(<ThemeContext.Provider />) === ReactIs.ContextProvider; // true
|
||||
ReactIs.typeOf(<ThemeContext.Consumer />) === ReactIs.ContextConsumer; // true
|
||||
```
|
||||
|
||||
### Element
|
||||
#### Element
|
||||
|
||||
```js
|
||||
import React from "react";
|
||||
@@ -48,7 +78,7 @@ ReactIs.isElement(<div />); // true
|
||||
ReactIs.typeOf(<div />) === ReactIs.Element; // true
|
||||
```
|
||||
|
||||
### Fragment
|
||||
#### Fragment
|
||||
|
||||
```js
|
||||
import React from "react";
|
||||
@@ -58,7 +88,7 @@ ReactIs.isFragment(<></>); // true
|
||||
ReactIs.typeOf(<></>) === ReactIs.Fragment; // true
|
||||
```
|
||||
|
||||
### Portal
|
||||
#### Portal
|
||||
|
||||
```js
|
||||
import React from "react";
|
||||
@@ -72,7 +102,7 @@ ReactIs.isPortal(portal); // true
|
||||
ReactIs.typeOf(portal) === ReactIs.Portal; // true
|
||||
```
|
||||
|
||||
### StrictMode
|
||||
#### StrictMode
|
||||
|
||||
```js
|
||||
import React from "react";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "react-is",
|
||||
"version": "16.3.0-alpha.2",
|
||||
"version": "16.3.1",
|
||||
"description": "Brand checking of React Elements.",
|
||||
"main": "index.js",
|
||||
"repository": "facebook/react",
|
||||
@@ -12,9 +12,6 @@
|
||||
"url": "https://github.com/facebook/react/issues"
|
||||
},
|
||||
"homepage": "https://reactjs.org/",
|
||||
"peerDependencies": {
|
||||
"react": "^16.0.0 || 16.3.0-alpha.2"
|
||||
},
|
||||
"files": [
|
||||
"LICENSE",
|
||||
"README.md",
|
||||
|
||||
5
packages/react-is/src/ReactIs.js
vendored
5
packages/react-is/src/ReactIs.js
vendored
@@ -19,6 +19,7 @@ import {
|
||||
REACT_PROVIDER_TYPE,
|
||||
REACT_STRICT_MODE_TYPE,
|
||||
} from 'shared/ReactSymbols';
|
||||
import isValidElementType from 'shared/isValidElementType';
|
||||
|
||||
export function typeOf(object: any) {
|
||||
if (typeof object === 'object' && object !== null) {
|
||||
@@ -34,7 +35,7 @@ export function typeOf(object: any) {
|
||||
case REACT_STRICT_MODE_TYPE:
|
||||
return type;
|
||||
default:
|
||||
const $$typeofType = type.$$typeof;
|
||||
const $$typeofType = type && type.$$typeof;
|
||||
|
||||
switch ($$typeofType) {
|
||||
case REACT_CONTEXT_TYPE:
|
||||
@@ -62,6 +63,8 @@ export const Fragment = REACT_FRAGMENT_TYPE;
|
||||
export const Portal = REACT_PORTAL_TYPE;
|
||||
export const StrictMode = REACT_STRICT_MODE_TYPE;
|
||||
|
||||
export {isValidElementType};
|
||||
|
||||
export function isAsyncMode(object: any) {
|
||||
return typeOf(object) === REACT_ASYNC_MODE_TYPE;
|
||||
}
|
||||
|
||||
36
packages/react-is/src/__tests__/ReactIs-test.js
vendored
36
packages/react-is/src/__tests__/ReactIs-test.js
vendored
@@ -31,6 +31,42 @@ describe('ReactIs', () => {
|
||||
expect(ReactIs.typeOf(undefined)).toBe(undefined);
|
||||
});
|
||||
|
||||
it('identifies valid element types', () => {
|
||||
class Component extends React.Component {
|
||||
render() {
|
||||
return React.createElement('div');
|
||||
}
|
||||
}
|
||||
|
||||
const StatelessComponent = () => React.createElement('div');
|
||||
|
||||
const ForwardRefComponent = React.forwardRef((props, ref) =>
|
||||
React.createElement(Component, {forwardedRef: ref, ...props}),
|
||||
);
|
||||
|
||||
const Context = React.createContext(false);
|
||||
|
||||
expect(ReactIs.isValidElementType('div')).toEqual(true);
|
||||
expect(ReactIs.isValidElementType(Component)).toEqual(true);
|
||||
expect(ReactIs.isValidElementType(StatelessComponent)).toEqual(true);
|
||||
expect(ReactIs.isValidElementType(ForwardRefComponent)).toEqual(true);
|
||||
expect(ReactIs.isValidElementType(Context.Provider)).toEqual(true);
|
||||
expect(ReactIs.isValidElementType(Context.Consumer)).toEqual(true);
|
||||
expect(ReactIs.isValidElementType(React.createFactory('div'))).toEqual(
|
||||
true,
|
||||
);
|
||||
expect(ReactIs.isValidElementType(React.Fragment)).toEqual(true);
|
||||
expect(ReactIs.isValidElementType(React.unstable_AsyncMode)).toEqual(true);
|
||||
expect(ReactIs.isValidElementType(React.StrictMode)).toEqual(true);
|
||||
|
||||
expect(ReactIs.isValidElementType(true)).toEqual(false);
|
||||
expect(ReactIs.isValidElementType(123)).toEqual(false);
|
||||
expect(ReactIs.isValidElementType({})).toEqual(false);
|
||||
expect(ReactIs.isValidElementType(null)).toEqual(false);
|
||||
expect(ReactIs.isValidElementType(undefined)).toEqual(false);
|
||||
expect(ReactIs.isValidElementType({type: 'div', props: {}})).toEqual(false);
|
||||
});
|
||||
|
||||
it('should identify async mode', () => {
|
||||
expect(ReactIs.typeOf(<React.unstable_AsyncMode />)).toBe(
|
||||
ReactIs.AsyncMode,
|
||||
|
||||
@@ -23,6 +23,7 @@ let frameDeadline: number = 0;
|
||||
|
||||
const frameDeadlineObject: Deadline = {
|
||||
timeRemaining: () => frameDeadline - now(),
|
||||
didTimeout: false,
|
||||
};
|
||||
|
||||
function setTimeoutCallback() {
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
"object-assign": "^4.1.1",
|
||||
"prop-types": "^15.6.0",
|
||||
"regenerator-runtime": "^0.11.0",
|
||||
"react-reconciler": "* || 0.8.0-alpha.2"
|
||||
"react-reconciler": "*"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.0.0"
|
||||
|
||||
@@ -308,6 +308,10 @@ function* flushUnitsOfWork(n: number): Generator<Array<mixed>, void, void> {
|
||||
didStop = true;
|
||||
return 0;
|
||||
},
|
||||
// React's scheduler has its own way of keeping track of expired
|
||||
// work and doesn't read this, so don't bother setting it to the
|
||||
// correct value.
|
||||
didTimeout: false,
|
||||
});
|
||||
|
||||
if (yieldedValues !== null) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "react-reconciler",
|
||||
"description": "React package for creating custom renderers.",
|
||||
"version": "0.8.0-alpha.2",
|
||||
"version": "0.9.0",
|
||||
"keywords": [
|
||||
"react"
|
||||
],
|
||||
@@ -22,7 +22,7 @@
|
||||
"node": ">=0.10.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.0.0 || 16.3.0-alpha.2"
|
||||
"react": "^16.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"fbjs": "^0.8.16",
|
||||
|
||||
@@ -31,7 +31,8 @@ type MeasurementPhase =
|
||||
| 'componentWillUpdate'
|
||||
| 'componentDidUpdate'
|
||||
| 'componentDidMount'
|
||||
| 'getChildContext';
|
||||
| 'getChildContext'
|
||||
| 'getSnapshotBeforeUpdate';
|
||||
|
||||
// Prefix measurements so that it's possible to filter them.
|
||||
// Longer prefixes are hard to read in DevTools.
|
||||
@@ -247,13 +248,18 @@ export function startRequestCallbackTimer(): void {
|
||||
}
|
||||
}
|
||||
|
||||
export function stopRequestCallbackTimer(didExpire: boolean): void {
|
||||
export function stopRequestCallbackTimer(
|
||||
didExpire: boolean,
|
||||
expirationTime: number,
|
||||
): void {
|
||||
if (enableUserTimingAPI) {
|
||||
if (supportsUserTiming) {
|
||||
isWaitingForCallback = false;
|
||||
const warning = didExpire ? 'React was blocked by main thread' : null;
|
||||
endMark(
|
||||
'(Waiting for async callback...)',
|
||||
`(Waiting for async callback... will force flush in ${
|
||||
expirationTime
|
||||
} ms)`,
|
||||
'(Waiting for async callback...)',
|
||||
warning,
|
||||
);
|
||||
@@ -363,7 +369,10 @@ export function startWorkLoopTimer(nextUnitOfWork: Fiber | null): void {
|
||||
}
|
||||
}
|
||||
|
||||
export function stopWorkLoopTimer(interruptedBy: Fiber | null): void {
|
||||
export function stopWorkLoopTimer(
|
||||
interruptedBy: Fiber | null,
|
||||
didCompleteRoot: boolean,
|
||||
): void {
|
||||
if (enableUserTimingAPI) {
|
||||
if (!supportsUserTiming) {
|
||||
return;
|
||||
@@ -382,13 +391,12 @@ export function stopWorkLoopTimer(interruptedBy: Fiber | null): void {
|
||||
warning = 'There were cascading updates';
|
||||
}
|
||||
commitCountInCurrentWorkLoop = 0;
|
||||
let label = didCompleteRoot
|
||||
? '(React Tree Reconciliation: Completed Root)'
|
||||
: '(React Tree Reconciliation: Yielded)';
|
||||
// Pause any measurements until the next loop.
|
||||
pauseTimers();
|
||||
endMark(
|
||||
'(React Tree Reconciliation)',
|
||||
'(React Tree Reconciliation)',
|
||||
warning,
|
||||
);
|
||||
endMark(label, '(React Tree Reconciliation)', warning);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -425,6 +433,31 @@ export function stopCommitTimer(): void {
|
||||
}
|
||||
}
|
||||
|
||||
export function startCommitSnapshotEffectsTimer(): void {
|
||||
if (enableUserTimingAPI) {
|
||||
if (!supportsUserTiming) {
|
||||
return;
|
||||
}
|
||||
effectCountInCurrentCommit = 0;
|
||||
beginMark('(Committing Snapshot Effects)');
|
||||
}
|
||||
}
|
||||
|
||||
export function stopCommitSnapshotEffectsTimer(): void {
|
||||
if (enableUserTimingAPI) {
|
||||
if (!supportsUserTiming) {
|
||||
return;
|
||||
}
|
||||
const count = effectCountInCurrentCommit;
|
||||
effectCountInCurrentCommit = 0;
|
||||
endMark(
|
||||
`(Committing Snapshot Effects: ${count} Total)`,
|
||||
'(Committing Snapshot Effects)',
|
||||
null,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function startCommitHostEffectsTimer(): void {
|
||||
if (enableUserTimingAPI) {
|
||||
if (!supportsUserTiming) {
|
||||
|
||||
44
packages/react-reconciler/src/ReactFiber.js
vendored
44
packages/react-reconciler/src/ReactFiber.js
vendored
@@ -471,3 +471,47 @@ export function createFiberFromPortal(
|
||||
};
|
||||
return fiber;
|
||||
}
|
||||
|
||||
// Used for stashing WIP properties to replay failed work in DEV.
|
||||
export function assignFiberPropertiesInDEV(
|
||||
target: Fiber | null,
|
||||
source: Fiber,
|
||||
): Fiber {
|
||||
if (target === null) {
|
||||
// This Fiber's initial properties will always be overwritten.
|
||||
// We only use a Fiber to ensure the same hidden class so DEV isn't slow.
|
||||
target = createFiber(IndeterminateComponent, null, null, NoContext);
|
||||
}
|
||||
|
||||
// This is intentionally written as a list of all properties.
|
||||
// We tried to use Object.assign() instead but this is called in
|
||||
// the hottest path, and Object.assign() was too slow:
|
||||
// https://github.com/facebook/react/issues/12502
|
||||
// This code is DEV-only so size is not a concern.
|
||||
|
||||
target.tag = source.tag;
|
||||
target.key = source.key;
|
||||
target.type = source.type;
|
||||
target.stateNode = source.stateNode;
|
||||
target.return = source.return;
|
||||
target.child = source.child;
|
||||
target.sibling = source.sibling;
|
||||
target.index = source.index;
|
||||
target.ref = source.ref;
|
||||
target.pendingProps = source.pendingProps;
|
||||
target.memoizedProps = source.memoizedProps;
|
||||
target.updateQueue = source.updateQueue;
|
||||
target.memoizedState = source.memoizedState;
|
||||
target.mode = source.mode;
|
||||
target.effectTag = source.effectTag;
|
||||
target.nextEffect = source.nextEffect;
|
||||
target.firstEffect = source.firstEffect;
|
||||
target.lastEffect = source.lastEffect;
|
||||
target.expirationTime = source.expirationTime;
|
||||
target.alternate = source.alternate;
|
||||
target._debugID = source._debugID;
|
||||
target._debugSource = source._debugSource;
|
||||
target._debugOwner = source._debugOwner;
|
||||
target._debugIsCurrentlyTiming = source._debugIsCurrentlyTiming;
|
||||
return target;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@ import type {HostConfig} from 'react-reconciler';
|
||||
import type {ReactProviderType, ReactContext} from 'shared/ReactTypes';
|
||||
import type {Fiber} from 'react-reconciler/src/ReactFiber';
|
||||
import type {HostContext} from './ReactFiberHostContext';
|
||||
import type {LegacyContext} from './ReactFiberContext';
|
||||
import type {NewContext} from './ReactFiberNewContext';
|
||||
import type {HydrationContext} from './ReactFiberHydrationContext';
|
||||
import type {FiberRoot} from './ReactFiberRoot';
|
||||
import type {ExpirationTime} from './ReactFiberExpirationTime';
|
||||
@@ -57,15 +59,6 @@ import {
|
||||
cloneChildFibers,
|
||||
} from './ReactChildFiber';
|
||||
import {processUpdateQueue} from './ReactFiberUpdateQueue';
|
||||
import {
|
||||
getMaskedContext,
|
||||
getUnmaskedContext,
|
||||
hasContextChanged as hasLegacyContextChanged,
|
||||
pushContextProvider as pushLegacyContextProvider,
|
||||
pushTopLevelContextObject,
|
||||
invalidateContextProvider,
|
||||
} from './ReactFiberContext';
|
||||
import {pushProvider} from './ReactFiberNewContext';
|
||||
import {NoWork, Never} from './ReactFiberExpirationTime';
|
||||
import {AsyncMode, StrictMode} from './ReactTypeOfMode';
|
||||
import MAX_SIGNED_31_BIT_INT from './maxSigned31BitInt';
|
||||
@@ -83,6 +76,8 @@ if (__DEV__) {
|
||||
export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
|
||||
config: HostConfig<T, P, I, TI, HI, PI, C, CC, CX, PL>,
|
||||
hostContext: HostContext<C, CX>,
|
||||
legacyContext: LegacyContext,
|
||||
newContext: NewContext,
|
||||
hydrationContext: HydrationContext<C, CX>,
|
||||
scheduleWork: (fiber: Fiber, expirationTime: ExpirationTime) => void,
|
||||
computeExpirationForFiber: (fiber: Fiber) => ExpirationTime,
|
||||
@@ -91,6 +86,17 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
|
||||
|
||||
const {pushHostContext, pushHostContainer} = hostContext;
|
||||
|
||||
const {pushProvider} = newContext;
|
||||
|
||||
const {
|
||||
getMaskedContext,
|
||||
getUnmaskedContext,
|
||||
hasContextChanged: hasLegacyContextChanged,
|
||||
pushContextProvider: pushLegacyContextProvider,
|
||||
pushTopLevelContextObject,
|
||||
invalidateContextProvider,
|
||||
} = legacyContext;
|
||||
|
||||
const {
|
||||
enterHydrationState,
|
||||
resetHydrationState,
|
||||
@@ -105,6 +111,7 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
|
||||
resumeMountClassInstance,
|
||||
updateClassInstance,
|
||||
} = ReactFiberClassComponent(
|
||||
legacyContext,
|
||||
scheduleWork,
|
||||
computeExpirationForFiber,
|
||||
memoizeProps,
|
||||
@@ -605,6 +612,7 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
|
||||
workInProgress,
|
||||
value,
|
||||
props,
|
||||
workInProgress.memoizedState,
|
||||
);
|
||||
|
||||
if (partialState !== null && partialState !== undefined) {
|
||||
@@ -769,6 +777,10 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
|
||||
renderExpirationTime: ExpirationTime,
|
||||
): void {
|
||||
let fiber = workInProgress.child;
|
||||
if (fiber !== null) {
|
||||
// Set the return pointer of the child to the work-in-progress fiber.
|
||||
fiber.return = workInProgress;
|
||||
}
|
||||
while (fiber !== null) {
|
||||
let nextFiber;
|
||||
// Visit this fiber.
|
||||
@@ -856,7 +868,7 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
|
||||
renderExpirationTime,
|
||||
) {
|
||||
const providerType: ReactProviderType<any> = workInProgress.type;
|
||||
const context: ReactContext<any> = providerType.context;
|
||||
const context: ReactContext<any> = providerType._context;
|
||||
|
||||
const newProps = workInProgress.pendingProps;
|
||||
const oldProps = workInProgress.memoizedProps;
|
||||
@@ -982,10 +994,10 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
|
||||
changedBits,
|
||||
renderExpirationTime,
|
||||
);
|
||||
} else if (oldProps !== null && oldProps.children === newProps.children) {
|
||||
// No change. Bailout early if children are the same.
|
||||
return bailoutOnAlreadyFinishedWork(current, workInProgress);
|
||||
}
|
||||
// There is no bailout on `children` equality because we expect people
|
||||
// to often pass a bound method as a child, but it may reference
|
||||
// `this.state` or `this.props` (and thus needs to re-render on `setState`).
|
||||
|
||||
const render = newProps.children;
|
||||
|
||||
|
||||
@@ -9,9 +9,10 @@
|
||||
|
||||
import type {Fiber} from './ReactFiber';
|
||||
import type {ExpirationTime} from './ReactFiberExpirationTime';
|
||||
import type {LegacyContext} from './ReactFiberContext';
|
||||
import type {CapturedValue} from './ReactCapturedValue';
|
||||
|
||||
import {Update} from 'shared/ReactTypeOfSideEffect';
|
||||
import {Update, Snapshot} from 'shared/ReactTypeOfSideEffect';
|
||||
import {
|
||||
enableGetDerivedStateFromCatch,
|
||||
debugRenderPhaseSideEffects,
|
||||
@@ -29,17 +30,10 @@ import warning from 'fbjs/lib/warning';
|
||||
|
||||
import {startPhaseTimer, stopPhaseTimer} from './ReactDebugFiberPerf';
|
||||
import {StrictMode} from './ReactTypeOfMode';
|
||||
import {
|
||||
cacheContext,
|
||||
getMaskedContext,
|
||||
getUnmaskedContext,
|
||||
isContextConsumer,
|
||||
} from './ReactFiberContext';
|
||||
import {
|
||||
insertUpdateIntoFiber,
|
||||
processUpdateQueue,
|
||||
} from './ReactFiberUpdateQueue';
|
||||
import {hasContextChanged} from './ReactFiberContext';
|
||||
|
||||
const fakeInternalInstance = {};
|
||||
const isArray = Array.isArray;
|
||||
@@ -47,23 +41,26 @@ const isArray = Array.isArray;
|
||||
let didWarnAboutStateAssignmentForComponent;
|
||||
let didWarnAboutUndefinedDerivedState;
|
||||
let didWarnAboutUninitializedState;
|
||||
let didWarnAboutWillReceivePropsAndDerivedState;
|
||||
let didWarnAboutGetSnapshotBeforeUpdateWithoutDidUpdate;
|
||||
let didWarnAboutLegacyLifecyclesAndDerivedState;
|
||||
let warnOnInvalidCallback;
|
||||
|
||||
if (__DEV__) {
|
||||
didWarnAboutStateAssignmentForComponent = {};
|
||||
didWarnAboutUndefinedDerivedState = {};
|
||||
didWarnAboutUninitializedState = {};
|
||||
didWarnAboutWillReceivePropsAndDerivedState = {};
|
||||
didWarnAboutStateAssignmentForComponent = new Set();
|
||||
didWarnAboutUndefinedDerivedState = new Set();
|
||||
didWarnAboutUninitializedState = new Set();
|
||||
didWarnAboutGetSnapshotBeforeUpdateWithoutDidUpdate = new Set();
|
||||
didWarnAboutLegacyLifecyclesAndDerivedState = new Set();
|
||||
|
||||
const didWarnOnInvalidCallback = {};
|
||||
const didWarnOnInvalidCallback = new Set();
|
||||
|
||||
warnOnInvalidCallback = function(callback: mixed, callerName: string) {
|
||||
if (callback === null || typeof callback === 'function') {
|
||||
return;
|
||||
}
|
||||
const key = `${callerName}_${(callback: any)}`;
|
||||
if (!didWarnOnInvalidCallback[key]) {
|
||||
if (!didWarnOnInvalidCallback.has(key)) {
|
||||
didWarnOnInvalidCallback.add(key);
|
||||
warning(
|
||||
false,
|
||||
'%s(...): Expected the last optional `callback` argument to be a ' +
|
||||
@@ -71,7 +68,6 @@ if (__DEV__) {
|
||||
callerName,
|
||||
callback,
|
||||
);
|
||||
didWarnOnInvalidCallback[key] = true;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -110,11 +106,20 @@ function callGetDerivedStateFromCatch(ctor: any, capturedValues: Array<mixed>) {
|
||||
}
|
||||
|
||||
export default function(
|
||||
legacyContext: LegacyContext,
|
||||
scheduleWork: (fiber: Fiber, expirationTime: ExpirationTime) => void,
|
||||
computeExpirationForFiber: (fiber: Fiber) => ExpirationTime,
|
||||
memoizeProps: (workInProgress: Fiber, props: any) => void,
|
||||
memoizeState: (workInProgress: Fiber, state: any) => void,
|
||||
) {
|
||||
const {
|
||||
cacheContext,
|
||||
getMaskedContext,
|
||||
getUnmaskedContext,
|
||||
isContextConsumer,
|
||||
hasContextChanged,
|
||||
} = legacyContext;
|
||||
|
||||
// Class component state updater
|
||||
const updater = {
|
||||
isMounted,
|
||||
@@ -210,7 +215,7 @@ export default function(
|
||||
shouldUpdate !== undefined,
|
||||
'%s.shouldComponentUpdate(): Returned undefined instead of a ' +
|
||||
'boolean value. Make sure to return true or false.',
|
||||
getComponentName(workInProgress) || 'Unknown',
|
||||
getComponentName(workInProgress) || 'Component',
|
||||
);
|
||||
}
|
||||
|
||||
@@ -230,7 +235,7 @@ export default function(
|
||||
const instance = workInProgress.stateNode;
|
||||
const type = workInProgress.type;
|
||||
if (__DEV__) {
|
||||
const name = getComponentName(workInProgress);
|
||||
const name = getComponentName(workInProgress) || 'Component';
|
||||
const renderPresent = instance.render;
|
||||
|
||||
if (!renderPresent) {
|
||||
@@ -361,23 +366,58 @@ export default function(
|
||||
name,
|
||||
name,
|
||||
);
|
||||
}
|
||||
|
||||
const state = instance.state;
|
||||
if (state && (typeof state !== 'object' || isArray(state))) {
|
||||
if (
|
||||
typeof instance.getSnapshotBeforeUpdate === 'function' &&
|
||||
typeof instance.componentDidUpdate !== 'function' &&
|
||||
typeof instance.componentDidUpdate !== 'function' &&
|
||||
!didWarnAboutGetSnapshotBeforeUpdateWithoutDidUpdate.has(type)
|
||||
) {
|
||||
didWarnAboutGetSnapshotBeforeUpdateWithoutDidUpdate.add(type);
|
||||
warning(
|
||||
false,
|
||||
'%s: getSnapshotBeforeUpdate() should be used with componentDidUpdate(). ' +
|
||||
'This component defines getSnapshotBeforeUpdate() only.',
|
||||
getComponentName(workInProgress),
|
||||
);
|
||||
}
|
||||
|
||||
const noInstanceGetDerivedStateFromProps =
|
||||
typeof instance.getDerivedStateFromProps !== 'function';
|
||||
warning(
|
||||
false,
|
||||
'%s.state: must be set to an object or null',
|
||||
getComponentName(workInProgress),
|
||||
noInstanceGetDerivedStateFromProps,
|
||||
'%s: getDerivedStateFromProps() is defined as an instance method ' +
|
||||
'and will be ignored. Instead, declare it as a static method.',
|
||||
name,
|
||||
);
|
||||
}
|
||||
if (typeof instance.getChildContext === 'function') {
|
||||
const noInstanceGetDerivedStateFromCatch =
|
||||
typeof instance.getDerivedStateFromCatch !== 'function';
|
||||
warning(
|
||||
typeof type.childContextTypes === 'object',
|
||||
'%s.getChildContext(): childContextTypes must be defined in order to ' +
|
||||
'use getChildContext().',
|
||||
getComponentName(workInProgress),
|
||||
noInstanceGetDerivedStateFromCatch,
|
||||
'%s: getDerivedStateFromCatch() is defined as an instance method ' +
|
||||
'and will be ignored. Instead, declare it as a static method.',
|
||||
name,
|
||||
);
|
||||
const noStaticGetSnapshotBeforeUpdate =
|
||||
typeof type.getSnapshotBeforeUpdate !== 'function';
|
||||
warning(
|
||||
noStaticGetSnapshotBeforeUpdate,
|
||||
'%s: getSnapshotBeforeUpdate() is defined as a static method ' +
|
||||
'and will be ignored. Instead, declare it as an instance method.',
|
||||
name,
|
||||
);
|
||||
const state = instance.state;
|
||||
if (state && (typeof state !== 'object' || isArray(state))) {
|
||||
warning(false, '%s.state: must be set to an object or null', name);
|
||||
}
|
||||
if (typeof instance.getChildContext === 'function') {
|
||||
warning(
|
||||
typeof type.childContextTypes === 'object',
|
||||
'%s.getChildContext(): childContextTypes must be defined in order to ' +
|
||||
'use getChildContext().',
|
||||
name,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -425,8 +465,9 @@ export default function(
|
||||
typeof ctor.getDerivedStateFromProps === 'function' &&
|
||||
state === null
|
||||
) {
|
||||
const componentName = getComponentName(workInProgress) || 'Unknown';
|
||||
if (!didWarnAboutUninitializedState[componentName]) {
|
||||
const componentName = getComponentName(workInProgress) || 'Component';
|
||||
if (!didWarnAboutUninitializedState.has(componentName)) {
|
||||
didWarnAboutUninitializedState.add(componentName);
|
||||
warning(
|
||||
false,
|
||||
'%s: Did not properly initialize state during construction. ' +
|
||||
@@ -434,7 +475,73 @@ export default function(
|
||||
componentName,
|
||||
instance.state === null ? 'null' : 'undefined',
|
||||
);
|
||||
didWarnAboutUninitializedState[componentName] = true;
|
||||
}
|
||||
}
|
||||
|
||||
// If new component APIs are defined, "unsafe" lifecycles won't be called.
|
||||
// Warn about these lifecycles if they are present.
|
||||
// Don't warn about react-lifecycles-compat polyfilled methods though.
|
||||
if (
|
||||
typeof ctor.getDerivedStateFromProps === 'function' ||
|
||||
typeof instance.getSnapshotBeforeUpdate === 'function'
|
||||
) {
|
||||
let foundWillMountName = null;
|
||||
let foundWillReceivePropsName = null;
|
||||
let foundWillUpdateName = null;
|
||||
if (
|
||||
typeof instance.componentWillMount === 'function' &&
|
||||
instance.componentWillMount.__suppressDeprecationWarning !== true
|
||||
) {
|
||||
foundWillMountName = 'componentWillMount';
|
||||
} else if (typeof instance.UNSAFE_componentWillMount === 'function') {
|
||||
foundWillMountName = 'UNSAFE_componentWillMount';
|
||||
}
|
||||
if (
|
||||
typeof instance.componentWillReceiveProps === 'function' &&
|
||||
instance.componentWillReceiveProps.__suppressDeprecationWarning !==
|
||||
true
|
||||
) {
|
||||
foundWillReceivePropsName = 'componentWillReceiveProps';
|
||||
} else if (
|
||||
typeof instance.UNSAFE_componentWillReceiveProps === 'function'
|
||||
) {
|
||||
foundWillReceivePropsName = 'UNSAFE_componentWillReceiveProps';
|
||||
}
|
||||
if (
|
||||
typeof instance.componentWillUpdate === 'function' &&
|
||||
instance.componentWillUpdate.__suppressDeprecationWarning !== true
|
||||
) {
|
||||
foundWillUpdateName = 'componentWillUpdate';
|
||||
} else if (typeof instance.UNSAFE_componentWillUpdate === 'function') {
|
||||
foundWillUpdateName = 'UNSAFE_componentWillUpdate';
|
||||
}
|
||||
if (
|
||||
foundWillMountName !== null ||
|
||||
foundWillReceivePropsName !== null ||
|
||||
foundWillUpdateName !== null
|
||||
) {
|
||||
const componentName = getComponentName(workInProgress) || 'Component';
|
||||
const newApiName =
|
||||
typeof ctor.getDerivedStateFromProps === 'function'
|
||||
? 'getDerivedStateFromProps()'
|
||||
: 'getSnapshotBeforeUpdate()';
|
||||
if (!didWarnAboutLegacyLifecyclesAndDerivedState.has(componentName)) {
|
||||
didWarnAboutLegacyLifecyclesAndDerivedState.add(componentName);
|
||||
warning(
|
||||
false,
|
||||
'Unsafe legacy lifecycles will not be called for components using new component APIs.\n\n' +
|
||||
'%s uses %s but also contains the following legacy lifecycles:%s%s%s\n\n' +
|
||||
'The above lifecycles should be removed. Learn more about this warning here:\n' +
|
||||
'https://fb.me/react-async-component-lifecycle-hooks',
|
||||
componentName,
|
||||
newApiName,
|
||||
foundWillMountName !== null ? `\n ${foundWillMountName}` : '',
|
||||
foundWillReceivePropsName !== null
|
||||
? `\n ${foundWillReceivePropsName}`
|
||||
: '',
|
||||
foundWillUpdateName !== null ? `\n ${foundWillUpdateName}` : '',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -445,6 +552,7 @@ export default function(
|
||||
workInProgress,
|
||||
instance,
|
||||
props,
|
||||
state,
|
||||
);
|
||||
|
||||
if (partialState !== null && partialState !== undefined) {
|
||||
@@ -487,7 +595,7 @@ export default function(
|
||||
'%s.componentWillMount(): Assigning directly to this.state is ' +
|
||||
"deprecated (except inside a component's " +
|
||||
'constructor). Use setState instead.',
|
||||
getComponentName(workInProgress),
|
||||
getComponentName(workInProgress) || 'Component',
|
||||
);
|
||||
}
|
||||
updater.enqueueReplaceState(instance, instance.state, null);
|
||||
@@ -513,7 +621,8 @@ export default function(
|
||||
if (instance.state !== oldState) {
|
||||
if (__DEV__) {
|
||||
const componentName = getComponentName(workInProgress) || 'Component';
|
||||
if (!didWarnAboutStateAssignmentForComponent[componentName]) {
|
||||
if (!didWarnAboutStateAssignmentForComponent.has(componentName)) {
|
||||
didWarnAboutStateAssignmentForComponent.add(componentName);
|
||||
warning(
|
||||
false,
|
||||
'%s.componentWillReceiveProps(): Assigning directly to ' +
|
||||
@@ -521,7 +630,6 @@ export default function(
|
||||
'constructor). Use setState instead.',
|
||||
componentName,
|
||||
);
|
||||
didWarnAboutStateAssignmentForComponent[componentName] = true;
|
||||
}
|
||||
}
|
||||
updater.enqueueReplaceState(instance, instance.state, null);
|
||||
@@ -531,63 +639,38 @@ export default function(
|
||||
function callGetDerivedStateFromProps(
|
||||
workInProgress: Fiber,
|
||||
instance: any,
|
||||
props: any,
|
||||
nextProps: any,
|
||||
prevState: any,
|
||||
) {
|
||||
const {type} = workInProgress;
|
||||
|
||||
if (typeof type.getDerivedStateFromProps === 'function') {
|
||||
if (__DEV__) {
|
||||
// Don't warn about react-lifecycles-compat polyfilled components
|
||||
if (
|
||||
(typeof instance.componentWillReceiveProps === 'function' &&
|
||||
instance.componentWillReceiveProps.__suppressDeprecationWarning !==
|
||||
true) ||
|
||||
typeof instance.UNSAFE_componentWillReceiveProps === 'function'
|
||||
) {
|
||||
const componentName = getComponentName(workInProgress) || 'Unknown';
|
||||
if (!didWarnAboutWillReceivePropsAndDerivedState[componentName]) {
|
||||
warning(
|
||||
false,
|
||||
'%s: Defines both componentWillReceiveProps() and static ' +
|
||||
'getDerivedStateFromProps() methods. We recommend using ' +
|
||||
'only getDerivedStateFromProps().',
|
||||
componentName,
|
||||
);
|
||||
didWarnAboutWillReceivePropsAndDerivedState[componentName] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
debugRenderPhaseSideEffects ||
|
||||
(debugRenderPhaseSideEffectsForStrictMode &&
|
||||
workInProgress.mode & StrictMode)
|
||||
) {
|
||||
// Invoke method an extra time to help detect side-effects.
|
||||
type.getDerivedStateFromProps.call(
|
||||
null,
|
||||
props,
|
||||
workInProgress.memoizedState,
|
||||
);
|
||||
type.getDerivedStateFromProps.call(null, nextProps, prevState);
|
||||
}
|
||||
|
||||
const partialState = type.getDerivedStateFromProps.call(
|
||||
null,
|
||||
props,
|
||||
workInProgress.memoizedState,
|
||||
nextProps,
|
||||
prevState,
|
||||
);
|
||||
|
||||
if (__DEV__) {
|
||||
if (partialState === undefined) {
|
||||
const componentName = getComponentName(workInProgress) || 'Unknown';
|
||||
if (!didWarnAboutUndefinedDerivedState[componentName]) {
|
||||
const componentName = getComponentName(workInProgress) || 'Component';
|
||||
if (!didWarnAboutUndefinedDerivedState.has(componentName)) {
|
||||
didWarnAboutUndefinedDerivedState.add(componentName);
|
||||
warning(
|
||||
false,
|
||||
'%s.getDerivedStateFromProps(): A valid state object (or null) must be returned. ' +
|
||||
'You have returned undefined.',
|
||||
componentName,
|
||||
);
|
||||
didWarnAboutUndefinedDerivedState[componentName] = componentName;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -634,11 +717,12 @@ export default function(
|
||||
}
|
||||
|
||||
// In order to support react-lifecycles-compat polyfilled components,
|
||||
// Unsafe lifecycles should not be invoked for any component with the new gDSFP.
|
||||
// Unsafe lifecycles should not be invoked for components using the new APIs.
|
||||
if (
|
||||
typeof ctor.getDerivedStateFromProps !== 'function' &&
|
||||
typeof instance.getSnapshotBeforeUpdate !== 'function' &&
|
||||
(typeof instance.UNSAFE_componentWillMount === 'function' ||
|
||||
typeof instance.componentWillMount === 'function') &&
|
||||
typeof ctor.getDerivedStateFromProps !== 'function'
|
||||
typeof instance.componentWillMount === 'function')
|
||||
) {
|
||||
callComponentWillMount(workInProgress, instance);
|
||||
// If we had additional state updates during this life-cycle, let's
|
||||
@@ -674,16 +758,20 @@ export default function(
|
||||
const newUnmaskedContext = getUnmaskedContext(workInProgress);
|
||||
const newContext = getMaskedContext(workInProgress, newUnmaskedContext);
|
||||
|
||||
const hasNewLifecycles =
|
||||
typeof ctor.getDerivedStateFromProps === 'function' ||
|
||||
typeof instance.getSnapshotBeforeUpdate === 'function';
|
||||
|
||||
// Note: During these life-cycles, instance.props/instance.state are what
|
||||
// ever the previously attempted to render - not the "current". However,
|
||||
// during componentDidUpdate we pass the "current" props.
|
||||
|
||||
// In order to support react-lifecycles-compat polyfilled components,
|
||||
// Unsafe lifecycles should not be invoked for any component with the new gDSFP.
|
||||
// Unsafe lifecycles should not be invoked for components using the new APIs.
|
||||
if (
|
||||
!hasNewLifecycles &&
|
||||
(typeof instance.UNSAFE_componentWillReceiveProps === 'function' ||
|
||||
typeof instance.componentWillReceiveProps === 'function') &&
|
||||
typeof ctor.getDerivedStateFromProps !== 'function'
|
||||
typeof instance.componentWillReceiveProps === 'function')
|
||||
) {
|
||||
if (oldProps !== newProps || oldContext !== newContext) {
|
||||
callComponentWillReceiveProps(
|
||||
@@ -695,15 +783,6 @@ export default function(
|
||||
}
|
||||
}
|
||||
|
||||
let derivedStateFromProps;
|
||||
if (oldProps !== newProps) {
|
||||
derivedStateFromProps = callGetDerivedStateFromProps(
|
||||
workInProgress,
|
||||
instance,
|
||||
newProps,
|
||||
);
|
||||
}
|
||||
|
||||
// Compute the next state using the memoized state and the update queue.
|
||||
const oldState = workInProgress.memoizedState;
|
||||
// TODO: Previous state can be null.
|
||||
@@ -740,6 +819,18 @@ export default function(
|
||||
newState = oldState;
|
||||
}
|
||||
|
||||
let derivedStateFromProps;
|
||||
if (oldProps !== newProps) {
|
||||
// The prevState parameter should be the partially updated state.
|
||||
// Otherwise, spreading state in return values could override updates.
|
||||
derivedStateFromProps = callGetDerivedStateFromProps(
|
||||
workInProgress,
|
||||
instance,
|
||||
newProps,
|
||||
newState,
|
||||
);
|
||||
}
|
||||
|
||||
if (derivedStateFromProps !== null && derivedStateFromProps !== undefined) {
|
||||
// Render-phase updates (like this) should not be added to the update queue,
|
||||
// So that multiple render passes do not enqueue multiple updates.
|
||||
@@ -748,6 +839,17 @@ export default function(
|
||||
newState === null || newState === undefined
|
||||
? derivedStateFromProps
|
||||
: Object.assign({}, newState, derivedStateFromProps);
|
||||
|
||||
// Update the base state of the update queue.
|
||||
// FIXME: This is getting ridiculous. Refactor plz!
|
||||
const updateQueue = workInProgress.updateQueue;
|
||||
if (updateQueue !== null) {
|
||||
updateQueue.baseState = Object.assign(
|
||||
{},
|
||||
updateQueue.baseState,
|
||||
derivedStateFromProps,
|
||||
);
|
||||
}
|
||||
}
|
||||
if (derivedStateFromCatch !== null && derivedStateFromCatch !== undefined) {
|
||||
// Render-phase updates (like this) should not be added to the update queue,
|
||||
@@ -757,6 +859,17 @@ export default function(
|
||||
newState === null || newState === undefined
|
||||
? derivedStateFromCatch
|
||||
: Object.assign({}, newState, derivedStateFromCatch);
|
||||
|
||||
// Update the base state of the update queue.
|
||||
// FIXME: This is getting ridiculous. Refactor plz!
|
||||
const updateQueue = workInProgress.updateQueue;
|
||||
if (updateQueue !== null) {
|
||||
updateQueue.baseState = Object.assign(
|
||||
{},
|
||||
updateQueue.baseState,
|
||||
derivedStateFromCatch,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
@@ -787,11 +900,11 @@ export default function(
|
||||
|
||||
if (shouldUpdate) {
|
||||
// In order to support react-lifecycles-compat polyfilled components,
|
||||
// Unsafe lifecycles should not be invoked for any component with the new gDSFP.
|
||||
// Unsafe lifecycles should not be invoked for components using the new APIs.
|
||||
if (
|
||||
!hasNewLifecycles &&
|
||||
(typeof instance.UNSAFE_componentWillMount === 'function' ||
|
||||
typeof instance.componentWillMount === 'function') &&
|
||||
typeof ctor.getDerivedStateFromProps !== 'function'
|
||||
typeof instance.componentWillMount === 'function')
|
||||
) {
|
||||
startPhaseTimer(workInProgress, 'componentWillMount');
|
||||
if (typeof instance.componentWillMount === 'function') {
|
||||
@@ -843,16 +956,20 @@ export default function(
|
||||
const newUnmaskedContext = getUnmaskedContext(workInProgress);
|
||||
const newContext = getMaskedContext(workInProgress, newUnmaskedContext);
|
||||
|
||||
const hasNewLifecycles =
|
||||
typeof ctor.getDerivedStateFromProps === 'function' ||
|
||||
typeof instance.getSnapshotBeforeUpdate === 'function';
|
||||
|
||||
// Note: During these life-cycles, instance.props/instance.state are what
|
||||
// ever the previously attempted to render - not the "current". However,
|
||||
// during componentDidUpdate we pass the "current" props.
|
||||
|
||||
// In order to support react-lifecycles-compat polyfilled components,
|
||||
// Unsafe lifecycles should not be invoked for any component with the new gDSFP.
|
||||
// Unsafe lifecycles should not be invoked for components using the new APIs.
|
||||
if (
|
||||
!hasNewLifecycles &&
|
||||
(typeof instance.UNSAFE_componentWillReceiveProps === 'function' ||
|
||||
typeof instance.componentWillReceiveProps === 'function') &&
|
||||
typeof ctor.getDerivedStateFromProps !== 'function'
|
||||
typeof instance.componentWillReceiveProps === 'function')
|
||||
) {
|
||||
if (oldProps !== newProps || oldContext !== newContext) {
|
||||
callComponentWillReceiveProps(
|
||||
@@ -864,20 +981,12 @@ export default function(
|
||||
}
|
||||
}
|
||||
|
||||
let derivedStateFromProps;
|
||||
if (oldProps !== newProps) {
|
||||
derivedStateFromProps = callGetDerivedStateFromProps(
|
||||
workInProgress,
|
||||
instance,
|
||||
newProps,
|
||||
);
|
||||
}
|
||||
|
||||
// Compute the next state using the memoized state and the update queue.
|
||||
const oldState = workInProgress.memoizedState;
|
||||
// TODO: Previous state can be null.
|
||||
let newState;
|
||||
let derivedStateFromCatch;
|
||||
|
||||
if (workInProgress.updateQueue !== null) {
|
||||
newState = processUpdateQueue(
|
||||
current,
|
||||
@@ -909,6 +1018,18 @@ export default function(
|
||||
newState = oldState;
|
||||
}
|
||||
|
||||
let derivedStateFromProps;
|
||||
if (oldProps !== newProps) {
|
||||
// The prevState parameter should be the partially updated state.
|
||||
// Otherwise, spreading state in return values could override updates.
|
||||
derivedStateFromProps = callGetDerivedStateFromProps(
|
||||
workInProgress,
|
||||
instance,
|
||||
newProps,
|
||||
newState,
|
||||
);
|
||||
}
|
||||
|
||||
if (derivedStateFromProps !== null && derivedStateFromProps !== undefined) {
|
||||
// Render-phase updates (like this) should not be added to the update queue,
|
||||
// So that multiple render passes do not enqueue multiple updates.
|
||||
@@ -917,6 +1038,17 @@ export default function(
|
||||
newState === null || newState === undefined
|
||||
? derivedStateFromProps
|
||||
: Object.assign({}, newState, derivedStateFromProps);
|
||||
|
||||
// Update the base state of the update queue.
|
||||
// FIXME: This is getting ridiculous. Refactor plz!
|
||||
const updateQueue = workInProgress.updateQueue;
|
||||
if (updateQueue !== null) {
|
||||
updateQueue.baseState = Object.assign(
|
||||
{},
|
||||
updateQueue.baseState,
|
||||
derivedStateFromProps,
|
||||
);
|
||||
}
|
||||
}
|
||||
if (derivedStateFromCatch !== null && derivedStateFromCatch !== undefined) {
|
||||
// Render-phase updates (like this) should not be added to the update queue,
|
||||
@@ -926,6 +1058,17 @@ export default function(
|
||||
newState === null || newState === undefined
|
||||
? derivedStateFromCatch
|
||||
: Object.assign({}, newState, derivedStateFromCatch);
|
||||
|
||||
// Update the base state of the update queue.
|
||||
// FIXME: This is getting ridiculous. Refactor plz!
|
||||
const updateQueue = workInProgress.updateQueue;
|
||||
if (updateQueue !== null) {
|
||||
updateQueue.baseState = Object.assign(
|
||||
{},
|
||||
updateQueue.baseState,
|
||||
derivedStateFromCatch,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
@@ -947,6 +1090,14 @@ export default function(
|
||||
workInProgress.effectTag |= Update;
|
||||
}
|
||||
}
|
||||
if (typeof instance.getSnapshotBeforeUpdate === 'function') {
|
||||
if (
|
||||
oldProps !== current.memoizedProps ||
|
||||
oldState !== current.memoizedState
|
||||
) {
|
||||
workInProgress.effectTag |= Snapshot;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -961,11 +1112,11 @@ export default function(
|
||||
|
||||
if (shouldUpdate) {
|
||||
// In order to support react-lifecycles-compat polyfilled components,
|
||||
// Unsafe lifecycles should not be invoked for any component with the new gDSFP.
|
||||
// Unsafe lifecycles should not be invoked for components using the new APIs.
|
||||
if (
|
||||
!hasNewLifecycles &&
|
||||
(typeof instance.UNSAFE_componentWillUpdate === 'function' ||
|
||||
typeof instance.componentWillUpdate === 'function') &&
|
||||
typeof ctor.getDerivedStateFromProps !== 'function'
|
||||
typeof instance.componentWillUpdate === 'function')
|
||||
) {
|
||||
startPhaseTimer(workInProgress, 'componentWillUpdate');
|
||||
if (typeof instance.componentWillUpdate === 'function') {
|
||||
@@ -979,6 +1130,9 @@ export default function(
|
||||
if (typeof instance.componentDidUpdate === 'function') {
|
||||
workInProgress.effectTag |= Update;
|
||||
}
|
||||
if (typeof instance.getSnapshotBeforeUpdate === 'function') {
|
||||
workInProgress.effectTag |= Snapshot;
|
||||
}
|
||||
} else {
|
||||
// If an update was already in progress, we should schedule an Update
|
||||
// effect even though we're bailing out, so that cWU/cDU are called.
|
||||
@@ -990,6 +1144,14 @@ export default function(
|
||||
workInProgress.effectTag |= Update;
|
||||
}
|
||||
}
|
||||
if (typeof instance.getSnapshotBeforeUpdate === 'function') {
|
||||
if (
|
||||
oldProps !== current.memoizedProps ||
|
||||
oldState !== current.memoizedState
|
||||
) {
|
||||
workInProgress.effectTag |= Snapshot;
|
||||
}
|
||||
}
|
||||
|
||||
// If shouldComponentUpdate returned false, we should still update the
|
||||
// memoized props/state to indicate that this work can be reused.
|
||||
|
||||
@@ -27,7 +27,12 @@ import {
|
||||
CallComponent,
|
||||
} from 'shared/ReactTypeOfWork';
|
||||
import ReactErrorUtils from 'shared/ReactErrorUtils';
|
||||
import {Placement, Update, ContentReset} from 'shared/ReactTypeOfSideEffect';
|
||||
import {
|
||||
Placement,
|
||||
Update,
|
||||
ContentReset,
|
||||
Snapshot,
|
||||
} from 'shared/ReactTypeOfSideEffect';
|
||||
import invariant from 'fbjs/lib/invariant';
|
||||
import warning from 'fbjs/lib/warning';
|
||||
|
||||
@@ -44,6 +49,11 @@ const {
|
||||
clearCaughtError,
|
||||
} = ReactErrorUtils;
|
||||
|
||||
let didWarnAboutUndefinedSnapshotBeforeUpdate: Set<mixed> | null = null;
|
||||
if (__DEV__) {
|
||||
didWarnAboutUndefinedSnapshotBeforeUpdate = new Set();
|
||||
}
|
||||
|
||||
function logError(boundary: Fiber, errorInfo: CapturedValue<mixed>) {
|
||||
const source = errorInfo.source;
|
||||
let stack = errorInfo.stack;
|
||||
@@ -53,21 +63,19 @@ function logError(boundary: Fiber, errorInfo: CapturedValue<mixed>) {
|
||||
|
||||
const capturedError: CapturedError = {
|
||||
componentName: source !== null ? getComponentName(source) : null,
|
||||
error: errorInfo.value,
|
||||
errorBoundary: boundary,
|
||||
componentStack: stack !== null ? stack : '',
|
||||
error: errorInfo.value,
|
||||
errorBoundary: null,
|
||||
errorBoundaryName: null,
|
||||
errorBoundaryFound: false,
|
||||
willRetry: false,
|
||||
};
|
||||
|
||||
if (boundary !== null) {
|
||||
if (boundary !== null && boundary.tag === ClassComponent) {
|
||||
capturedError.errorBoundary = boundary.stateNode;
|
||||
capturedError.errorBoundaryName = getComponentName(boundary);
|
||||
capturedError.errorBoundaryFound = capturedError.willRetry =
|
||||
boundary.tag === ClassComponent;
|
||||
} else {
|
||||
capturedError.errorBoundaryName = null;
|
||||
capturedError.errorBoundaryFound = capturedError.willRetry = false;
|
||||
capturedError.errorBoundaryFound = true;
|
||||
capturedError.willRetry = true;
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -153,6 +161,63 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
|
||||
}
|
||||
}
|
||||
|
||||
function commitBeforeMutationLifeCycles(
|
||||
current: Fiber | null,
|
||||
finishedWork: Fiber,
|
||||
): void {
|
||||
switch (finishedWork.tag) {
|
||||
case ClassComponent: {
|
||||
if (finishedWork.effectTag & Snapshot) {
|
||||
if (current !== null) {
|
||||
const prevProps = current.memoizedProps;
|
||||
const prevState = current.memoizedState;
|
||||
startPhaseTimer(finishedWork, 'getSnapshotBeforeUpdate');
|
||||
const instance = finishedWork.stateNode;
|
||||
instance.props = finishedWork.memoizedProps;
|
||||
instance.state = finishedWork.memoizedState;
|
||||
const snapshot = instance.getSnapshotBeforeUpdate(
|
||||
prevProps,
|
||||
prevState,
|
||||
);
|
||||
if (__DEV__) {
|
||||
const didWarnSet = ((didWarnAboutUndefinedSnapshotBeforeUpdate: any): Set<
|
||||
mixed,
|
||||
>);
|
||||
if (
|
||||
snapshot === undefined &&
|
||||
!didWarnSet.has(finishedWork.type)
|
||||
) {
|
||||
didWarnSet.add(finishedWork.type);
|
||||
warning(
|
||||
false,
|
||||
'%s.getSnapshotBeforeUpdate(): A snapshot value (or null) ' +
|
||||
'must be returned. You have returned undefined.',
|
||||
getComponentName(finishedWork),
|
||||
);
|
||||
}
|
||||
}
|
||||
instance.__reactInternalSnapshotBeforeUpdate = snapshot;
|
||||
stopPhaseTimer();
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
case HostRoot:
|
||||
case HostComponent:
|
||||
case HostText:
|
||||
case HostPortal:
|
||||
// Nothing to do for these component types
|
||||
return;
|
||||
default: {
|
||||
invariant(
|
||||
false,
|
||||
'This unit of work tag should not have side-effects. This error is ' +
|
||||
'likely caused by a bug in React. Please file an issue.',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function commitLifeCycles(
|
||||
finishedRoot: FiberRoot,
|
||||
current: Fiber | null,
|
||||
@@ -176,7 +241,11 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
|
||||
startPhaseTimer(finishedWork, 'componentDidUpdate');
|
||||
instance.props = finishedWork.memoizedProps;
|
||||
instance.state = finishedWork.memoizedState;
|
||||
instance.componentDidUpdate(prevProps, prevState);
|
||||
instance.componentDidUpdate(
|
||||
prevProps,
|
||||
prevState,
|
||||
instance.__reactInternalSnapshotBeforeUpdate,
|
||||
);
|
||||
stopPhaseTimer();
|
||||
}
|
||||
}
|
||||
@@ -270,8 +339,11 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
|
||||
for (let i = 0; i < capturedErrors.length; i++) {
|
||||
const errorInfo = capturedErrors[i];
|
||||
const error = errorInfo.value;
|
||||
const stack = errorInfo.stack;
|
||||
logError(finishedWork, errorInfo);
|
||||
instance.componentDidCatch(error);
|
||||
instance.componentDidCatch(error, {
|
||||
componentStack: stack !== null ? stack : '',
|
||||
});
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -321,7 +393,7 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
|
||||
warning(
|
||||
false,
|
||||
'Unexpected ref object provided for %s. ' +
|
||||
'Use either a ref-setter function or Reacte.createRef().%s',
|
||||
'Use either a ref-setter function or React.createRef().%s',
|
||||
getComponentName(finishedWork),
|
||||
getStackAddendumByWorkInProgressFiber(finishedWork),
|
||||
);
|
||||
@@ -491,6 +563,7 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
|
||||
commitContainer(finishedWork);
|
||||
},
|
||||
commitLifeCycles,
|
||||
commitBeforeMutationLifeCycles,
|
||||
commitErrorLogging,
|
||||
commitAttachRef,
|
||||
commitDetachRef,
|
||||
@@ -813,6 +886,7 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
|
||||
|
||||
if (enableMutatingReconciler) {
|
||||
return {
|
||||
commitBeforeMutationLifeCycles,
|
||||
commitResetTextContent,
|
||||
commitPlacement,
|
||||
commitDeletion,
|
||||
|
||||
@@ -11,6 +11,8 @@ import type {HostConfig} from 'react-reconciler';
|
||||
import type {Fiber} from './ReactFiber';
|
||||
import type {ExpirationTime} from './ReactFiberExpirationTime';
|
||||
import type {HostContext} from './ReactFiberHostContext';
|
||||
import type {LegacyContext} from './ReactFiberContext';
|
||||
import type {NewContext} from './ReactFiberNewContext';
|
||||
import type {HydrationContext} from './ReactFiberHydrationContext';
|
||||
import type {FiberRoot} from './ReactFiberRoot';
|
||||
|
||||
@@ -46,15 +48,12 @@ import {
|
||||
import invariant from 'fbjs/lib/invariant';
|
||||
|
||||
import {reconcileChildFibers} from './ReactChildFiber';
|
||||
import {
|
||||
popContextProvider as popLegacyContextProvider,
|
||||
popTopLevelContextObject as popTopLevelLegacyContextObject,
|
||||
} from './ReactFiberContext';
|
||||
import {popProvider} from './ReactFiberNewContext';
|
||||
|
||||
export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
|
||||
config: HostConfig<T, P, I, TI, HI, PI, C, CC, CX, PL>,
|
||||
hostContext: HostContext<C, CX>,
|
||||
legacyContext: LegacyContext,
|
||||
newContext: NewContext,
|
||||
hydrationContext: HydrationContext<C, CX>,
|
||||
) {
|
||||
const {
|
||||
@@ -74,6 +73,13 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
|
||||
popHostContainer,
|
||||
} = hostContext;
|
||||
|
||||
const {
|
||||
popContextProvider: popLegacyContextProvider,
|
||||
popTopLevelContextObject: popTopLevelLegacyContextObject,
|
||||
} = legacyContext;
|
||||
|
||||
const {popProvider} = newContext;
|
||||
|
||||
const {
|
||||
prepareToHydrateHostInstance,
|
||||
prepareToHydrateHostTextInstance,
|
||||
|
||||
538
packages/react-reconciler/src/ReactFiberContext.js
vendored
538
packages/react-reconciler/src/ReactFiberContext.js
vendored
@@ -8,7 +8,7 @@
|
||||
*/
|
||||
|
||||
import type {Fiber} from './ReactFiber';
|
||||
import type {StackCursor} from './ReactFiberStack';
|
||||
import type {StackCursor, Stack} from './ReactFiberStack';
|
||||
|
||||
import {isFiberMounted} from 'react-reconciler/reflection';
|
||||
import {ClassComponent, HostRoot} from 'shared/ReactTypeOfWork';
|
||||
@@ -18,7 +18,6 @@ import invariant from 'fbjs/lib/invariant';
|
||||
import warning from 'fbjs/lib/warning';
|
||||
import checkPropTypes from 'prop-types/checkPropTypes';
|
||||
|
||||
import {createCursor, pop, push} from './ReactFiberStack';
|
||||
import ReactDebugCurrentFiber from './ReactDebugCurrentFiber';
|
||||
import {startPhaseTimer, stopPhaseTimer} from './ReactDebugFiberPerf';
|
||||
|
||||
@@ -28,273 +27,308 @@ if (__DEV__) {
|
||||
warnedAboutMissingGetChildContext = {};
|
||||
}
|
||||
|
||||
// A cursor to the current merged context object on the stack.
|
||||
let contextStackCursor: StackCursor<Object> = createCursor(emptyObject);
|
||||
// A cursor to a boolean indicating whether the context has changed.
|
||||
let didPerformWorkStackCursor: StackCursor<boolean> = createCursor(false);
|
||||
// Keep track of the previous context object that was on the stack.
|
||||
// We use this to get access to the parent context after we have already
|
||||
// pushed the next context provider, and now need to merge their contexts.
|
||||
let previousContext: Object = emptyObject;
|
||||
export type LegacyContext = {
|
||||
getUnmaskedContext(workInProgress: Fiber): Object,
|
||||
cacheContext(
|
||||
workInProgress: Fiber,
|
||||
unmaskedContext: Object,
|
||||
maskedContext: Object,
|
||||
): void,
|
||||
getMaskedContext(workInProgress: Fiber, unmaskedContext: Object): Object,
|
||||
hasContextChanged(): boolean,
|
||||
isContextConsumer(fiber: Fiber): boolean,
|
||||
isContextProvider(fiber: Fiber): boolean,
|
||||
popContextProvider(fiber: Fiber): void,
|
||||
popTopLevelContextObject(fiber: Fiber): void,
|
||||
pushTopLevelContextObject(
|
||||
fiber: Fiber,
|
||||
context: Object,
|
||||
didChange: boolean,
|
||||
): void,
|
||||
processChildContext(fiber: Fiber, parentContext: Object): Object,
|
||||
pushContextProvider(workInProgress: Fiber): boolean,
|
||||
invalidateContextProvider(workInProgress: Fiber, didChange: boolean): void,
|
||||
findCurrentUnmaskedContext(fiber: Fiber): Object,
|
||||
};
|
||||
|
||||
export function getUnmaskedContext(workInProgress: Fiber): Object {
|
||||
const hasOwnContext = isContextProvider(workInProgress);
|
||||
if (hasOwnContext) {
|
||||
// If the fiber is a context provider itself, when we read its context
|
||||
// we have already pushed its own child context on the stack. A context
|
||||
// provider should not "see" its own child context. Therefore we read the
|
||||
// previous (parent) context instead for a context provider.
|
||||
return previousContext;
|
||||
}
|
||||
return contextStackCursor.current;
|
||||
}
|
||||
export default function(stack: Stack): LegacyContext {
|
||||
const {createCursor, push, pop} = stack;
|
||||
|
||||
export function cacheContext(
|
||||
workInProgress: Fiber,
|
||||
unmaskedContext: Object,
|
||||
maskedContext: Object,
|
||||
) {
|
||||
const instance = workInProgress.stateNode;
|
||||
instance.__reactInternalMemoizedUnmaskedChildContext = unmaskedContext;
|
||||
instance.__reactInternalMemoizedMaskedChildContext = maskedContext;
|
||||
}
|
||||
// A cursor to the current merged context object on the stack.
|
||||
let contextStackCursor: StackCursor<Object> = createCursor(emptyObject);
|
||||
// A cursor to a boolean indicating whether the context has changed.
|
||||
let didPerformWorkStackCursor: StackCursor<boolean> = createCursor(false);
|
||||
// Keep track of the previous context object that was on the stack.
|
||||
// We use this to get access to the parent context after we have already
|
||||
// pushed the next context provider, and now need to merge their contexts.
|
||||
let previousContext: Object = emptyObject;
|
||||
|
||||
export function getMaskedContext(
|
||||
workInProgress: Fiber,
|
||||
unmaskedContext: Object,
|
||||
) {
|
||||
const type = workInProgress.type;
|
||||
const contextTypes = type.contextTypes;
|
||||
if (!contextTypes) {
|
||||
return emptyObject;
|
||||
function getUnmaskedContext(workInProgress: Fiber): Object {
|
||||
const hasOwnContext = isContextProvider(workInProgress);
|
||||
if (hasOwnContext) {
|
||||
// If the fiber is a context provider itself, when we read its context
|
||||
// we have already pushed its own child context on the stack. A context
|
||||
// provider should not "see" its own child context. Therefore we read the
|
||||
// previous (parent) context instead for a context provider.
|
||||
return previousContext;
|
||||
}
|
||||
return contextStackCursor.current;
|
||||
}
|
||||
|
||||
// Avoid recreating masked context unless unmasked context has changed.
|
||||
// Failing to do this will result in unnecessary calls to componentWillReceiveProps.
|
||||
// This may trigger infinite loops if componentWillReceiveProps calls setState.
|
||||
const instance = workInProgress.stateNode;
|
||||
if (
|
||||
instance &&
|
||||
instance.__reactInternalMemoizedUnmaskedChildContext === unmaskedContext
|
||||
function cacheContext(
|
||||
workInProgress: Fiber,
|
||||
unmaskedContext: Object,
|
||||
maskedContext: Object,
|
||||
) {
|
||||
return instance.__reactInternalMemoizedMaskedChildContext;
|
||||
const instance = workInProgress.stateNode;
|
||||
instance.__reactInternalMemoizedUnmaskedChildContext = unmaskedContext;
|
||||
instance.__reactInternalMemoizedMaskedChildContext = maskedContext;
|
||||
}
|
||||
|
||||
const context = {};
|
||||
for (let key in contextTypes) {
|
||||
context[key] = unmaskedContext[key];
|
||||
}
|
||||
function getMaskedContext(workInProgress: Fiber, unmaskedContext: Object) {
|
||||
const type = workInProgress.type;
|
||||
const contextTypes = type.contextTypes;
|
||||
if (!contextTypes) {
|
||||
return emptyObject;
|
||||
}
|
||||
|
||||
if (__DEV__) {
|
||||
const name = getComponentName(workInProgress) || 'Unknown';
|
||||
checkPropTypes(
|
||||
contextTypes,
|
||||
context,
|
||||
'context',
|
||||
name,
|
||||
ReactDebugCurrentFiber.getCurrentFiberStackAddendum,
|
||||
);
|
||||
}
|
||||
// Avoid recreating masked context unless unmasked context has changed.
|
||||
// Failing to do this will result in unnecessary calls to componentWillReceiveProps.
|
||||
// This may trigger infinite loops if componentWillReceiveProps calls setState.
|
||||
const instance = workInProgress.stateNode;
|
||||
if (
|
||||
instance &&
|
||||
instance.__reactInternalMemoizedUnmaskedChildContext === unmaskedContext
|
||||
) {
|
||||
return instance.__reactInternalMemoizedMaskedChildContext;
|
||||
}
|
||||
|
||||
// Cache unmasked context so we can avoid recreating masked context unless necessary.
|
||||
// Context is created before the class component is instantiated so check for instance.
|
||||
if (instance) {
|
||||
cacheContext(workInProgress, unmaskedContext, context);
|
||||
}
|
||||
const context = {};
|
||||
for (let key in contextTypes) {
|
||||
context[key] = unmaskedContext[key];
|
||||
}
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
export function hasContextChanged(): boolean {
|
||||
return didPerformWorkStackCursor.current;
|
||||
}
|
||||
|
||||
export function isContextConsumer(fiber: Fiber): boolean {
|
||||
return fiber.tag === ClassComponent && fiber.type.contextTypes != null;
|
||||
}
|
||||
|
||||
export function isContextProvider(fiber: Fiber): boolean {
|
||||
return fiber.tag === ClassComponent && fiber.type.childContextTypes != null;
|
||||
}
|
||||
|
||||
export function popContextProvider(fiber: Fiber): void {
|
||||
if (!isContextProvider(fiber)) {
|
||||
return;
|
||||
}
|
||||
|
||||
pop(didPerformWorkStackCursor, fiber);
|
||||
pop(contextStackCursor, fiber);
|
||||
}
|
||||
|
||||
export function popTopLevelContextObject(fiber: Fiber) {
|
||||
pop(didPerformWorkStackCursor, fiber);
|
||||
pop(contextStackCursor, fiber);
|
||||
}
|
||||
|
||||
export function pushTopLevelContextObject(
|
||||
fiber: Fiber,
|
||||
context: Object,
|
||||
didChange: boolean,
|
||||
): void {
|
||||
invariant(
|
||||
contextStackCursor.cursor == null,
|
||||
'Unexpected context found on stack. ' +
|
||||
'This error is likely caused by a bug in React. Please file an issue.',
|
||||
);
|
||||
|
||||
push(contextStackCursor, context, fiber);
|
||||
push(didPerformWorkStackCursor, didChange, fiber);
|
||||
}
|
||||
|
||||
export function processChildContext(
|
||||
fiber: Fiber,
|
||||
parentContext: Object,
|
||||
): Object {
|
||||
const instance = fiber.stateNode;
|
||||
const childContextTypes = fiber.type.childContextTypes;
|
||||
|
||||
// TODO (bvaughn) Replace this behavior with an invariant() in the future.
|
||||
// It has only been added in Fiber to match the (unintentional) behavior in Stack.
|
||||
if (typeof instance.getChildContext !== 'function') {
|
||||
if (__DEV__) {
|
||||
const componentName = getComponentName(fiber) || 'Unknown';
|
||||
|
||||
if (!warnedAboutMissingGetChildContext[componentName]) {
|
||||
warnedAboutMissingGetChildContext[componentName] = true;
|
||||
warning(
|
||||
false,
|
||||
'%s.childContextTypes is specified but there is no getChildContext() method ' +
|
||||
'on the instance. You can either define getChildContext() on %s or remove ' +
|
||||
'childContextTypes from it.',
|
||||
componentName,
|
||||
componentName,
|
||||
);
|
||||
}
|
||||
const name = getComponentName(workInProgress) || 'Unknown';
|
||||
checkPropTypes(
|
||||
contextTypes,
|
||||
context,
|
||||
'context',
|
||||
name,
|
||||
ReactDebugCurrentFiber.getCurrentFiberStackAddendum,
|
||||
);
|
||||
}
|
||||
return parentContext;
|
||||
}
|
||||
|
||||
let childContext;
|
||||
if (__DEV__) {
|
||||
ReactDebugCurrentFiber.setCurrentPhase('getChildContext');
|
||||
}
|
||||
startPhaseTimer(fiber, 'getChildContext');
|
||||
childContext = instance.getChildContext();
|
||||
stopPhaseTimer();
|
||||
if (__DEV__) {
|
||||
ReactDebugCurrentFiber.setCurrentPhase(null);
|
||||
}
|
||||
for (let contextKey in childContext) {
|
||||
invariant(
|
||||
contextKey in childContextTypes,
|
||||
'%s.getChildContext(): key "%s" is not defined in childContextTypes.',
|
||||
getComponentName(fiber) || 'Unknown',
|
||||
contextKey,
|
||||
);
|
||||
}
|
||||
if (__DEV__) {
|
||||
const name = getComponentName(fiber) || 'Unknown';
|
||||
checkPropTypes(
|
||||
childContextTypes,
|
||||
childContext,
|
||||
'child context',
|
||||
name,
|
||||
// In practice, there is one case in which we won't get a stack. It's when
|
||||
// somebody calls unstable_renderSubtreeIntoContainer() and we process
|
||||
// context from the parent component instance. The stack will be missing
|
||||
// because it's outside of the reconciliation, and so the pointer has not
|
||||
// been set. This is rare and doesn't matter. We'll also remove that API.
|
||||
ReactDebugCurrentFiber.getCurrentFiberStackAddendum,
|
||||
);
|
||||
}
|
||||
|
||||
return {...parentContext, ...childContext};
|
||||
}
|
||||
|
||||
export function pushContextProvider(workInProgress: Fiber): boolean {
|
||||
if (!isContextProvider(workInProgress)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const instance = workInProgress.stateNode;
|
||||
// We push the context as early as possible to ensure stack integrity.
|
||||
// If the instance does not exist yet, we will push null at first,
|
||||
// and replace it on the stack later when invalidating the context.
|
||||
const memoizedMergedChildContext =
|
||||
(instance && instance.__reactInternalMemoizedMergedChildContext) ||
|
||||
emptyObject;
|
||||
|
||||
// Remember the parent context so we can merge with it later.
|
||||
// Inherit the parent's did-perform-work value to avoid inadvertently blocking updates.
|
||||
previousContext = contextStackCursor.current;
|
||||
push(contextStackCursor, memoizedMergedChildContext, workInProgress);
|
||||
push(
|
||||
didPerformWorkStackCursor,
|
||||
didPerformWorkStackCursor.current,
|
||||
workInProgress,
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
export function invalidateContextProvider(
|
||||
workInProgress: Fiber,
|
||||
didChange: boolean,
|
||||
): void {
|
||||
const instance = workInProgress.stateNode;
|
||||
invariant(
|
||||
instance,
|
||||
'Expected to have an instance by this point. ' +
|
||||
'This error is likely caused by a bug in React. Please file an issue.',
|
||||
);
|
||||
|
||||
if (didChange) {
|
||||
// Merge parent and own context.
|
||||
// Skip this if we're not updating due to sCU.
|
||||
// This avoids unnecessarily recomputing memoized values.
|
||||
const mergedContext = processChildContext(workInProgress, previousContext);
|
||||
instance.__reactInternalMemoizedMergedChildContext = mergedContext;
|
||||
|
||||
// Replace the old (or empty) context with the new one.
|
||||
// It is important to unwind the context in the reverse order.
|
||||
pop(didPerformWorkStackCursor, workInProgress);
|
||||
pop(contextStackCursor, workInProgress);
|
||||
// Now push the new context and mark that it has changed.
|
||||
push(contextStackCursor, mergedContext, workInProgress);
|
||||
push(didPerformWorkStackCursor, didChange, workInProgress);
|
||||
} else {
|
||||
pop(didPerformWorkStackCursor, workInProgress);
|
||||
push(didPerformWorkStackCursor, didChange, workInProgress);
|
||||
}
|
||||
}
|
||||
|
||||
export function resetContext(): void {
|
||||
previousContext = emptyObject;
|
||||
contextStackCursor.current = emptyObject;
|
||||
didPerformWorkStackCursor.current = false;
|
||||
}
|
||||
|
||||
export function findCurrentUnmaskedContext(fiber: Fiber): Object {
|
||||
// Currently this is only used with renderSubtreeIntoContainer; not sure if it
|
||||
// makes sense elsewhere
|
||||
invariant(
|
||||
isFiberMounted(fiber) && fiber.tag === ClassComponent,
|
||||
'Expected subtree parent to be a mounted class component. ' +
|
||||
'This error is likely caused by a bug in React. Please file an issue.',
|
||||
);
|
||||
|
||||
let node: Fiber = fiber;
|
||||
while (node.tag !== HostRoot) {
|
||||
if (isContextProvider(node)) {
|
||||
return node.stateNode.__reactInternalMemoizedMergedChildContext;
|
||||
// Cache unmasked context so we can avoid recreating masked context unless necessary.
|
||||
// Context is created before the class component is instantiated so check for instance.
|
||||
if (instance) {
|
||||
cacheContext(workInProgress, unmaskedContext, context);
|
||||
}
|
||||
const parent = node.return;
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
function hasContextChanged(): boolean {
|
||||
return didPerformWorkStackCursor.current;
|
||||
}
|
||||
|
||||
function isContextConsumer(fiber: Fiber): boolean {
|
||||
return fiber.tag === ClassComponent && fiber.type.contextTypes != null;
|
||||
}
|
||||
|
||||
function isContextProvider(fiber: Fiber): boolean {
|
||||
return fiber.tag === ClassComponent && fiber.type.childContextTypes != null;
|
||||
}
|
||||
|
||||
function popContextProvider(fiber: Fiber): void {
|
||||
if (!isContextProvider(fiber)) {
|
||||
return;
|
||||
}
|
||||
|
||||
pop(didPerformWorkStackCursor, fiber);
|
||||
pop(contextStackCursor, fiber);
|
||||
}
|
||||
|
||||
function popTopLevelContextObject(fiber: Fiber) {
|
||||
pop(didPerformWorkStackCursor, fiber);
|
||||
pop(contextStackCursor, fiber);
|
||||
}
|
||||
|
||||
function pushTopLevelContextObject(
|
||||
fiber: Fiber,
|
||||
context: Object,
|
||||
didChange: boolean,
|
||||
): void {
|
||||
invariant(
|
||||
parent,
|
||||
'Found unexpected detached subtree parent. ' +
|
||||
contextStackCursor.cursor == null,
|
||||
'Unexpected context found on stack. ' +
|
||||
'This error is likely caused by a bug in React. Please file an issue.',
|
||||
);
|
||||
node = parent;
|
||||
|
||||
push(contextStackCursor, context, fiber);
|
||||
push(didPerformWorkStackCursor, didChange, fiber);
|
||||
}
|
||||
return node.stateNode.context;
|
||||
|
||||
function processChildContext(fiber: Fiber, parentContext: Object): Object {
|
||||
const instance = fiber.stateNode;
|
||||
const childContextTypes = fiber.type.childContextTypes;
|
||||
|
||||
// TODO (bvaughn) Replace this behavior with an invariant() in the future.
|
||||
// It has only been added in Fiber to match the (unintentional) behavior in Stack.
|
||||
if (typeof instance.getChildContext !== 'function') {
|
||||
if (__DEV__) {
|
||||
const componentName = getComponentName(fiber) || 'Unknown';
|
||||
|
||||
if (!warnedAboutMissingGetChildContext[componentName]) {
|
||||
warnedAboutMissingGetChildContext[componentName] = true;
|
||||
warning(
|
||||
false,
|
||||
'%s.childContextTypes is specified but there is no getChildContext() method ' +
|
||||
'on the instance. You can either define getChildContext() on %s or remove ' +
|
||||
'childContextTypes from it.',
|
||||
componentName,
|
||||
componentName,
|
||||
);
|
||||
}
|
||||
}
|
||||
return parentContext;
|
||||
}
|
||||
|
||||
let childContext;
|
||||
if (__DEV__) {
|
||||
ReactDebugCurrentFiber.setCurrentPhase('getChildContext');
|
||||
}
|
||||
startPhaseTimer(fiber, 'getChildContext');
|
||||
childContext = instance.getChildContext();
|
||||
stopPhaseTimer();
|
||||
if (__DEV__) {
|
||||
ReactDebugCurrentFiber.setCurrentPhase(null);
|
||||
}
|
||||
for (let contextKey in childContext) {
|
||||
invariant(
|
||||
contextKey in childContextTypes,
|
||||
'%s.getChildContext(): key "%s" is not defined in childContextTypes.',
|
||||
getComponentName(fiber) || 'Unknown',
|
||||
contextKey,
|
||||
);
|
||||
}
|
||||
if (__DEV__) {
|
||||
const name = getComponentName(fiber) || 'Unknown';
|
||||
checkPropTypes(
|
||||
childContextTypes,
|
||||
childContext,
|
||||
'child context',
|
||||
name,
|
||||
// In practice, there is one case in which we won't get a stack. It's when
|
||||
// somebody calls unstable_renderSubtreeIntoContainer() and we process
|
||||
// context from the parent component instance. The stack will be missing
|
||||
// because it's outside of the reconciliation, and so the pointer has not
|
||||
// been set. This is rare and doesn't matter. We'll also remove that API.
|
||||
ReactDebugCurrentFiber.getCurrentFiberStackAddendum,
|
||||
);
|
||||
}
|
||||
|
||||
return {...parentContext, ...childContext};
|
||||
}
|
||||
|
||||
function pushContextProvider(workInProgress: Fiber): boolean {
|
||||
if (!isContextProvider(workInProgress)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const instance = workInProgress.stateNode;
|
||||
// We push the context as early as possible to ensure stack integrity.
|
||||
// If the instance does not exist yet, we will push null at first,
|
||||
// and replace it on the stack later when invalidating the context.
|
||||
const memoizedMergedChildContext =
|
||||
(instance && instance.__reactInternalMemoizedMergedChildContext) ||
|
||||
emptyObject;
|
||||
|
||||
// Remember the parent context so we can merge with it later.
|
||||
// Inherit the parent's did-perform-work value to avoid inadvertently blocking updates.
|
||||
previousContext = contextStackCursor.current;
|
||||
push(contextStackCursor, memoizedMergedChildContext, workInProgress);
|
||||
push(
|
||||
didPerformWorkStackCursor,
|
||||
didPerformWorkStackCursor.current,
|
||||
workInProgress,
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function invalidateContextProvider(
|
||||
workInProgress: Fiber,
|
||||
didChange: boolean,
|
||||
): void {
|
||||
const instance = workInProgress.stateNode;
|
||||
invariant(
|
||||
instance,
|
||||
'Expected to have an instance by this point. ' +
|
||||
'This error is likely caused by a bug in React. Please file an issue.',
|
||||
);
|
||||
|
||||
if (didChange) {
|
||||
// Merge parent and own context.
|
||||
// Skip this if we're not updating due to sCU.
|
||||
// This avoids unnecessarily recomputing memoized values.
|
||||
const mergedContext = processChildContext(
|
||||
workInProgress,
|
||||
previousContext,
|
||||
);
|
||||
instance.__reactInternalMemoizedMergedChildContext = mergedContext;
|
||||
|
||||
// Replace the old (or empty) context with the new one.
|
||||
// It is important to unwind the context in the reverse order.
|
||||
pop(didPerformWorkStackCursor, workInProgress);
|
||||
pop(contextStackCursor, workInProgress);
|
||||
// Now push the new context and mark that it has changed.
|
||||
push(contextStackCursor, mergedContext, workInProgress);
|
||||
push(didPerformWorkStackCursor, didChange, workInProgress);
|
||||
} else {
|
||||
pop(didPerformWorkStackCursor, workInProgress);
|
||||
push(didPerformWorkStackCursor, didChange, workInProgress);
|
||||
}
|
||||
}
|
||||
|
||||
function findCurrentUnmaskedContext(fiber: Fiber): Object {
|
||||
// Currently this is only used with renderSubtreeIntoContainer; not sure if it
|
||||
// makes sense elsewhere
|
||||
invariant(
|
||||
isFiberMounted(fiber) && fiber.tag === ClassComponent,
|
||||
'Expected subtree parent to be a mounted class component. ' +
|
||||
'This error is likely caused by a bug in React. Please file an issue.',
|
||||
);
|
||||
|
||||
let node: Fiber = fiber;
|
||||
while (node.tag !== HostRoot) {
|
||||
if (isContextProvider(node)) {
|
||||
return node.stateNode.__reactInternalMemoizedMergedChildContext;
|
||||
}
|
||||
const parent = node.return;
|
||||
invariant(
|
||||
parent,
|
||||
'Found unexpected detached subtree parent. ' +
|
||||
'This error is likely caused by a bug in React. Please file an issue.',
|
||||
);
|
||||
node = parent;
|
||||
}
|
||||
return node.stateNode.context;
|
||||
}
|
||||
|
||||
return {
|
||||
getUnmaskedContext,
|
||||
cacheContext,
|
||||
getMaskedContext,
|
||||
hasContextChanged,
|
||||
isContextConsumer,
|
||||
isContextProvider,
|
||||
popContextProvider,
|
||||
popTopLevelContextObject,
|
||||
pushTopLevelContextObject,
|
||||
processChildContext,
|
||||
pushContextProvider,
|
||||
invalidateContextProvider,
|
||||
findCurrentUnmaskedContext,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -9,12 +9,10 @@
|
||||
|
||||
import type {HostConfig} from 'react-reconciler';
|
||||
import type {Fiber} from './ReactFiber';
|
||||
import type {StackCursor} from './ReactFiberStack';
|
||||
import type {StackCursor, Stack} from './ReactFiberStack';
|
||||
|
||||
import invariant from 'fbjs/lib/invariant';
|
||||
|
||||
import {createCursor, pop, push} from './ReactFiberStack';
|
||||
|
||||
declare class NoContextT {}
|
||||
const NO_CONTEXT: NoContextT = ({}: any);
|
||||
|
||||
@@ -25,13 +23,14 @@ export type HostContext<C, CX> = {
|
||||
popHostContext(fiber: Fiber): void,
|
||||
pushHostContainer(fiber: Fiber, container: C): void,
|
||||
pushHostContext(fiber: Fiber): void,
|
||||
resetHostContainer(): void,
|
||||
};
|
||||
|
||||
export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
|
||||
config: HostConfig<T, P, I, TI, HI, PI, C, CC, CX, PL>,
|
||||
stack: Stack,
|
||||
): HostContext<C, CX> {
|
||||
const {getChildHostContext, getRootHostContext} = config;
|
||||
const {createCursor, push, pop} = stack;
|
||||
|
||||
let contextStackCursor: StackCursor<CX | NoContextT> = createCursor(
|
||||
NO_CONTEXT,
|
||||
@@ -61,12 +60,19 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
|
||||
// Push current root instance onto the stack;
|
||||
// This allows us to reset root when portals are popped.
|
||||
push(rootInstanceStackCursor, nextRootInstance, fiber);
|
||||
|
||||
const nextRootContext = getRootHostContext(nextRootInstance);
|
||||
|
||||
// Track the context and the Fiber that provided it.
|
||||
// This enables us to pop only Fibers that provide unique contexts.
|
||||
push(contextFiberStackCursor, fiber, fiber);
|
||||
|
||||
// Finally, we need to push the host context to the stack.
|
||||
// However, we can't just call getRootHostContext() and push it because
|
||||
// we'd have a different number of entries on the stack depending on
|
||||
// whether getRootHostContext() throws somewhere in renderer code or not.
|
||||
// So we push an empty value first. This lets us safely unwind on errors.
|
||||
push(contextStackCursor, NO_CONTEXT, fiber);
|
||||
const nextRootContext = getRootHostContext(nextRootInstance);
|
||||
// Now that we know this function doesn't throw, replace it.
|
||||
pop(contextStackCursor, fiber);
|
||||
push(contextStackCursor, nextRootContext, fiber);
|
||||
}
|
||||
|
||||
@@ -108,11 +114,6 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
|
||||
pop(contextFiberStackCursor, fiber);
|
||||
}
|
||||
|
||||
function resetHostContainer() {
|
||||
contextStackCursor.current = NO_CONTEXT;
|
||||
rootInstanceStackCursor.current = NO_CONTEXT;
|
||||
}
|
||||
|
||||
return {
|
||||
getHostContext,
|
||||
getRootHostContainer,
|
||||
@@ -120,6 +121,5 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
|
||||
popHostContext,
|
||||
pushHostContainer,
|
||||
pushHostContext,
|
||||
resetHostContainer,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -9,67 +9,64 @@
|
||||
|
||||
import type {Fiber} from './ReactFiber';
|
||||
import type {ReactContext} from 'shared/ReactTypes';
|
||||
import type {StackCursor, Stack} from './ReactFiberStack';
|
||||
|
||||
import warning from 'fbjs/lib/warning';
|
||||
|
||||
let changedBitsStack: Array<any> = [];
|
||||
let currentValueStack: Array<any> = [];
|
||||
let stack: Array<Fiber> = [];
|
||||
let index = -1;
|
||||
export type NewContext = {
|
||||
pushProvider(providerFiber: Fiber): void,
|
||||
popProvider(providerFiber: Fiber): void,
|
||||
};
|
||||
|
||||
let rendererSigil;
|
||||
if (__DEV__) {
|
||||
// Use this to detect multiple renderers using the same context
|
||||
rendererSigil = {};
|
||||
}
|
||||
export default function(stack: Stack) {
|
||||
const {createCursor, push, pop} = stack;
|
||||
|
||||
export function pushProvider(providerFiber: Fiber): void {
|
||||
const context: ReactContext<any> = providerFiber.type.context;
|
||||
index += 1;
|
||||
changedBitsStack[index] = context._changedBits;
|
||||
currentValueStack[index] = context._currentValue;
|
||||
stack[index] = providerFiber;
|
||||
context._currentValue = providerFiber.pendingProps.value;
|
||||
context._changedBits = providerFiber.stateNode;
|
||||
const providerCursor: StackCursor<Fiber | null> = createCursor(null);
|
||||
const valueCursor: StackCursor<mixed> = createCursor(null);
|
||||
const changedBitsCursor: StackCursor<number> = createCursor(0);
|
||||
|
||||
let rendererSigil;
|
||||
if (__DEV__) {
|
||||
warning(
|
||||
context._currentRenderer === null ||
|
||||
context._currentRenderer === rendererSigil,
|
||||
'Detected multiple renderers concurrently rendering the ' +
|
||||
'same context provider. This is currently unsupported.',
|
||||
);
|
||||
context._currentRenderer = rendererSigil;
|
||||
// Use this to detect multiple renderers using the same context
|
||||
rendererSigil = {};
|
||||
}
|
||||
}
|
||||
|
||||
export function popProvider(providerFiber: Fiber): void {
|
||||
if (__DEV__) {
|
||||
warning(index > -1 && providerFiber === stack[index], 'Unexpected pop.');
|
||||
}
|
||||
const changedBits = changedBitsStack[index];
|
||||
const currentValue = currentValueStack[index];
|
||||
changedBitsStack[index] = null;
|
||||
currentValueStack[index] = null;
|
||||
stack[index] = null;
|
||||
index -= 1;
|
||||
const context: ReactContext<any> = providerFiber.type.context;
|
||||
context._currentValue = currentValue;
|
||||
context._changedBits = changedBits;
|
||||
}
|
||||
function pushProvider(providerFiber: Fiber): void {
|
||||
const context: ReactContext<any> = providerFiber.type._context;
|
||||
|
||||
push(changedBitsCursor, context._changedBits, providerFiber);
|
||||
push(valueCursor, context._currentValue, providerFiber);
|
||||
push(providerCursor, providerFiber, providerFiber);
|
||||
|
||||
context._currentValue = providerFiber.pendingProps.value;
|
||||
context._changedBits = providerFiber.stateNode;
|
||||
|
||||
export function resetProviderStack(): void {
|
||||
for (let i = index; i > -1; i--) {
|
||||
const providerFiber = stack[i];
|
||||
const context: ReactContext<any> = providerFiber.type.context;
|
||||
context._currentValue = context._defaultValue;
|
||||
context._changedBits = 0;
|
||||
changedBitsStack[i] = null;
|
||||
currentValueStack[i] = null;
|
||||
stack[i] = null;
|
||||
if (__DEV__) {
|
||||
context._currentRenderer = null;
|
||||
warning(
|
||||
context._currentRenderer === null ||
|
||||
context._currentRenderer === rendererSigil,
|
||||
'Detected multiple renderers concurrently rendering the ' +
|
||||
'same context provider. This is currently unsupported.',
|
||||
);
|
||||
context._currentRenderer = rendererSigil;
|
||||
}
|
||||
}
|
||||
index = -1;
|
||||
|
||||
function popProvider(providerFiber: Fiber): void {
|
||||
const changedBits = changedBitsCursor.current;
|
||||
const currentValue = valueCursor.current;
|
||||
|
||||
pop(providerCursor, providerFiber);
|
||||
pop(valueCursor, providerFiber);
|
||||
pop(changedBitsCursor, providerFiber);
|
||||
|
||||
const context: ReactContext<any> = providerFiber.type._context;
|
||||
context._currentValue = currentValue;
|
||||
context._changedBits = changedBits;
|
||||
}
|
||||
|
||||
return {
|
||||
pushProvider,
|
||||
popProvider,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -22,11 +22,6 @@ import emptyObject from 'fbjs/lib/emptyObject';
|
||||
import getComponentName from 'shared/getComponentName';
|
||||
import warning from 'fbjs/lib/warning';
|
||||
|
||||
import {
|
||||
findCurrentUnmaskedContext,
|
||||
isContextProvider,
|
||||
processChildContext,
|
||||
} from './ReactFiberContext';
|
||||
import {createFiberRoot} from './ReactFiberRoot';
|
||||
import * as ReactFiberDevToolsHook from './ReactFiberDevToolsHook';
|
||||
import ReactFiberScheduler from './ReactFiberScheduler';
|
||||
@@ -42,6 +37,7 @@ if (__DEV__) {
|
||||
|
||||
export type Deadline = {
|
||||
timeRemaining: () => number,
|
||||
didTimeout: boolean,
|
||||
};
|
||||
|
||||
type OpaqueHandle = Fiber;
|
||||
@@ -274,20 +270,6 @@ export type Reconciler<C, I, TI> = {
|
||||
findHostInstanceWithNoPortals(component: Fiber): I | TI | null,
|
||||
};
|
||||
|
||||
function getContextForSubtree(
|
||||
parentComponent: ?React$Component<any, any>,
|
||||
): Object {
|
||||
if (!parentComponent) {
|
||||
return emptyObject;
|
||||
}
|
||||
|
||||
const fiber = ReactInstanceMap.get(parentComponent);
|
||||
const parentContext = findCurrentUnmaskedContext(fiber);
|
||||
return isContextProvider(fiber)
|
||||
? processChildContext(fiber, parentContext)
|
||||
: parentContext;
|
||||
}
|
||||
|
||||
export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
|
||||
config: HostConfig<T, P, I, TI, HI, PI, C, CC, CX, PL>,
|
||||
): Reconciler<C, I, TI> {
|
||||
@@ -308,8 +290,29 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
|
||||
syncUpdates,
|
||||
interactiveUpdates,
|
||||
flushInteractiveUpdates,
|
||||
legacyContext,
|
||||
} = ReactFiberScheduler(config);
|
||||
|
||||
const {
|
||||
findCurrentUnmaskedContext,
|
||||
isContextProvider,
|
||||
processChildContext,
|
||||
} = legacyContext;
|
||||
|
||||
function getContextForSubtree(
|
||||
parentComponent: ?React$Component<any, any>,
|
||||
): Object {
|
||||
if (!parentComponent) {
|
||||
return emptyObject;
|
||||
}
|
||||
|
||||
const fiber = ReactInstanceMap.get(parentComponent);
|
||||
const parentContext = findCurrentUnmaskedContext(fiber);
|
||||
return isContextProvider(fiber)
|
||||
? processChildContext(fiber, parentContext)
|
||||
: parentContext;
|
||||
}
|
||||
|
||||
function scheduleRootUpdate(
|
||||
current: Fiber,
|
||||
element: ReactNodeList,
|
||||
|
||||
212
packages/react-reconciler/src/ReactFiberScheduler.js
vendored
212
packages/react-reconciler/src/ReactFiberScheduler.js
vendored
@@ -14,6 +14,7 @@ import type {HydrationContext} from './ReactFiberHydrationContext';
|
||||
import type {ExpirationTime} from './ReactFiberExpirationTime';
|
||||
|
||||
import ReactErrorUtils from 'shared/ReactErrorUtils';
|
||||
import {getStackAddendumByWorkInProgressFiber} from 'shared/ReactFiberComponentTreeHook';
|
||||
import {ReactCurrentOwner} from 'shared/ReactGlobalSharedState';
|
||||
import ReactStrictModeWarnings from './ReactStrictModeWarnings';
|
||||
import {
|
||||
@@ -21,6 +22,7 @@ import {
|
||||
PerformedWork,
|
||||
Placement,
|
||||
Update,
|
||||
Snapshot,
|
||||
PlacementAndUpdate,
|
||||
Deletion,
|
||||
ContentReset,
|
||||
@@ -67,13 +69,14 @@ import {
|
||||
stopWorkLoopTimer,
|
||||
startCommitTimer,
|
||||
stopCommitTimer,
|
||||
startCommitSnapshotEffectsTimer,
|
||||
stopCommitSnapshotEffectsTimer,
|
||||
startCommitHostEffectsTimer,
|
||||
stopCommitHostEffectsTimer,
|
||||
startCommitLifeCyclesTimer,
|
||||
stopCommitLifeCyclesTimer,
|
||||
} from './ReactDebugFiberPerf';
|
||||
import {reset} from './ReactFiberStack';
|
||||
import {createWorkInProgress} from './ReactFiber';
|
||||
import {createWorkInProgress, assignFiberPropertiesInDEV} from './ReactFiber';
|
||||
import {onCommitRoot} from './ReactFiberDevToolsHook';
|
||||
import {
|
||||
NoWork,
|
||||
@@ -84,18 +87,14 @@ import {
|
||||
computeExpirationBucket,
|
||||
} from './ReactFiberExpirationTime';
|
||||
import {AsyncMode} from './ReactTypeOfMode';
|
||||
import {
|
||||
resetContext as resetLegacyContext,
|
||||
popContextProvider as popLegacyContextProvider,
|
||||
popTopLevelContextObject as popTopLevelLegacyContextObject,
|
||||
} from './ReactFiberContext';
|
||||
import {popProvider} from './ReactFiberNewContext';
|
||||
import {resetProviderStack} from './ReactFiberNewContext';
|
||||
import ReactFiberLegacyContext from './ReactFiberContext';
|
||||
import ReactFiberNewContext from './ReactFiberNewContext';
|
||||
import {
|
||||
getUpdateExpirationTime,
|
||||
insertUpdateIntoFiber,
|
||||
} from './ReactFiberUpdateQueue';
|
||||
import {createCapturedValue} from './ReactCapturedValue';
|
||||
import ReactFiberStack from './ReactFiberStack';
|
||||
|
||||
const {
|
||||
invokeGuardedCallback,
|
||||
@@ -114,17 +113,19 @@ if (__DEV__) {
|
||||
const didWarnStateUpdateForUnmountedComponent = {};
|
||||
|
||||
warnAboutUpdateOnUnmounted = function(fiber: Fiber) {
|
||||
// We show the whole stack but dedupe on the top component's name because
|
||||
// the problematic code almost always lies inside that component.
|
||||
const componentName = getComponentName(fiber) || 'ReactClass';
|
||||
if (didWarnStateUpdateForUnmountedComponent[componentName]) {
|
||||
return;
|
||||
}
|
||||
warning(
|
||||
false,
|
||||
'Can only update a mounted or mounting ' +
|
||||
'component. This usually means you called setState, replaceState, ' +
|
||||
'or forceUpdate on an unmounted component. This is a no-op.\n\nPlease ' +
|
||||
'check the code for the %s component.',
|
||||
componentName,
|
||||
"Can't call setState (or forceUpdate) on an unmounted component. This " +
|
||||
'is a no-op, but it indicates a memory leak in your application. To ' +
|
||||
'fix, cancel all subscriptions and asynchronous tasks in the ' +
|
||||
'componentWillUnmount method.%s',
|
||||
getStackAddendumByWorkInProgressFiber(fiber),
|
||||
);
|
||||
didWarnStateUpdateForUnmountedComponent[componentName] = true;
|
||||
};
|
||||
@@ -161,15 +162,24 @@ if (__DEV__) {
|
||||
export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
|
||||
config: HostConfig<T, P, I, TI, HI, PI, C, CC, CX, PL>,
|
||||
) {
|
||||
const hostContext = ReactFiberHostContext(config);
|
||||
const stack = ReactFiberStack();
|
||||
const hostContext = ReactFiberHostContext(config, stack);
|
||||
const legacyContext = ReactFiberLegacyContext(stack);
|
||||
const newContext = ReactFiberNewContext(stack);
|
||||
const {popHostContext, popHostContainer} = hostContext;
|
||||
const {
|
||||
popTopLevelContextObject: popTopLevelLegacyContextObject,
|
||||
popContextProvider: popLegacyContextProvider,
|
||||
} = legacyContext;
|
||||
const {popProvider} = newContext;
|
||||
const hydrationContext: HydrationContext<C, CX> = ReactFiberHydrationContext(
|
||||
config,
|
||||
);
|
||||
const {resetHostContainer} = hostContext;
|
||||
const {beginWork} = ReactFiberBeginWork(
|
||||
config,
|
||||
hostContext,
|
||||
legacyContext,
|
||||
newContext,
|
||||
hydrationContext,
|
||||
scheduleWork,
|
||||
computeExpirationForFiber,
|
||||
@@ -177,14 +187,23 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
|
||||
const {completeWork} = ReactFiberCompleteWork(
|
||||
config,
|
||||
hostContext,
|
||||
legacyContext,
|
||||
newContext,
|
||||
hydrationContext,
|
||||
);
|
||||
const {throwException, unwindWork} = ReactFiberUnwindWork(
|
||||
const {
|
||||
throwException,
|
||||
unwindWork,
|
||||
unwindInterruptedWork,
|
||||
} = ReactFiberUnwindWork(
|
||||
hostContext,
|
||||
legacyContext,
|
||||
newContext,
|
||||
scheduleWork,
|
||||
isAlreadyFailedLegacyErrorBoundary,
|
||||
);
|
||||
const {
|
||||
commitBeforeMutationLifeCycles,
|
||||
commitResetTextContent,
|
||||
commitPlacement,
|
||||
commitDeletion,
|
||||
@@ -244,11 +263,23 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
|
||||
|
||||
let stashedWorkInProgressProperties;
|
||||
let replayUnitOfWork;
|
||||
let isReplayingFailedUnitOfWork;
|
||||
let originalReplayError;
|
||||
let rethrowOriginalError;
|
||||
if (__DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback) {
|
||||
stashedWorkInProgressProperties = null;
|
||||
replayUnitOfWork = (failedUnitOfWork: Fiber, isAsync: boolean) => {
|
||||
// Retore the original state of the work-in-progress
|
||||
Object.assign(failedUnitOfWork, stashedWorkInProgressProperties);
|
||||
isReplayingFailedUnitOfWork = false;
|
||||
originalReplayError = null;
|
||||
replayUnitOfWork = (
|
||||
failedUnitOfWork: Fiber,
|
||||
error: mixed,
|
||||
isAsync: boolean,
|
||||
) => {
|
||||
// Restore the original state of the work-in-progress
|
||||
assignFiberPropertiesInDEV(
|
||||
failedUnitOfWork,
|
||||
stashedWorkInProgressProperties,
|
||||
);
|
||||
switch (failedUnitOfWork.tag) {
|
||||
case HostRoot:
|
||||
popHostContainer(failedUnitOfWork);
|
||||
@@ -268,28 +299,36 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
|
||||
break;
|
||||
}
|
||||
// Replay the begin phase.
|
||||
isReplayingFailedUnitOfWork = true;
|
||||
originalReplayError = error;
|
||||
invokeGuardedCallback(null, workLoop, null, isAsync);
|
||||
isReplayingFailedUnitOfWork = false;
|
||||
originalReplayError = null;
|
||||
if (hasCaughtError()) {
|
||||
clearCaughtError();
|
||||
} else {
|
||||
// This should be unreachable because the render phase is
|
||||
// idempotent
|
||||
// If the begin phase did not fail the second time, set this pointer
|
||||
// back to the original value.
|
||||
nextUnitOfWork = failedUnitOfWork;
|
||||
}
|
||||
};
|
||||
rethrowOriginalError = () => {
|
||||
throw originalReplayError;
|
||||
};
|
||||
}
|
||||
|
||||
function resetContextStack() {
|
||||
// Reset the stack
|
||||
reset();
|
||||
// Reset the cursors
|
||||
resetLegacyContext();
|
||||
resetHostContainer();
|
||||
|
||||
// TODO: Unify new context implementation with other stacks
|
||||
resetProviderStack();
|
||||
function resetStack() {
|
||||
if (nextUnitOfWork !== null) {
|
||||
let interruptedWork = nextUnitOfWork.return;
|
||||
while (interruptedWork !== null) {
|
||||
unwindInterruptedWork(interruptedWork);
|
||||
interruptedWork = interruptedWork.return;
|
||||
}
|
||||
}
|
||||
|
||||
if (__DEV__) {
|
||||
ReactStrictModeWarnings.discardPendingWarnings();
|
||||
stack.checkThatStackIsEmpty();
|
||||
}
|
||||
|
||||
nextRoot = null;
|
||||
@@ -307,6 +346,7 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
|
||||
recordEffect();
|
||||
|
||||
const effectTag = nextEffect.effectTag;
|
||||
|
||||
if (effectTag & ContentReset) {
|
||||
commitResetTextContent(nextEffect);
|
||||
}
|
||||
@@ -364,6 +404,22 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
|
||||
}
|
||||
}
|
||||
|
||||
function commitBeforeMutationLifecycles() {
|
||||
while (nextEffect !== null) {
|
||||
const effectTag = nextEffect.effectTag;
|
||||
|
||||
if (effectTag & Snapshot) {
|
||||
recordEffect();
|
||||
const current = nextEffect.alternate;
|
||||
commitBeforeMutationLifeCycles(current, nextEffect);
|
||||
}
|
||||
|
||||
// Don't cleanup effects yet;
|
||||
// This will be done by commitAllLifeCycles()
|
||||
nextEffect = nextEffect.nextEffect;
|
||||
}
|
||||
}
|
||||
|
||||
function commitAllLifeCycles(
|
||||
finishedRoot: FiberRoot,
|
||||
currentTime: ExpirationTime,
|
||||
@@ -471,6 +527,41 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
|
||||
|
||||
prepareForCommit(root.containerInfo);
|
||||
|
||||
// Invoke instances of getSnapshotBeforeUpdate before mutation.
|
||||
nextEffect = firstEffect;
|
||||
startCommitSnapshotEffectsTimer();
|
||||
while (nextEffect !== null) {
|
||||
let didError = false;
|
||||
let error;
|
||||
if (__DEV__) {
|
||||
invokeGuardedCallback(null, commitBeforeMutationLifecycles, null);
|
||||
if (hasCaughtError()) {
|
||||
didError = true;
|
||||
error = clearCaughtError();
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
commitBeforeMutationLifecycles();
|
||||
} catch (e) {
|
||||
didError = true;
|
||||
error = e;
|
||||
}
|
||||
}
|
||||
if (didError) {
|
||||
invariant(
|
||||
nextEffect !== null,
|
||||
'Should have next effect. This error is likely caused by a bug ' +
|
||||
'in React. Please file an issue.',
|
||||
);
|
||||
onCommitPhaseError(nextEffect, error);
|
||||
// Clean-up
|
||||
if (nextEffect !== null) {
|
||||
nextEffect = nextEffect.nextEffect;
|
||||
}
|
||||
}
|
||||
}
|
||||
stopCommitSnapshotEffectsTimer();
|
||||
|
||||
// Commit all the side-effects within a tree. We'll do this in two passes.
|
||||
// The first pass performs all the host insertions, updates, deletions and
|
||||
// ref unmounts.
|
||||
@@ -775,12 +866,21 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
|
||||
}
|
||||
|
||||
if (__DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback) {
|
||||
stashedWorkInProgressProperties = Object.assign({}, workInProgress);
|
||||
stashedWorkInProgressProperties = assignFiberPropertiesInDEV(
|
||||
stashedWorkInProgressProperties,
|
||||
workInProgress,
|
||||
);
|
||||
}
|
||||
let next = beginWork(current, workInProgress, nextRenderExpirationTime);
|
||||
|
||||
if (__DEV__) {
|
||||
ReactDebugCurrentFiber.resetCurrentFiber();
|
||||
if (isReplayingFailedUnitOfWork) {
|
||||
// Currently replaying a failed unit of work. This should be unreachable,
|
||||
// because the render phase is meant to be idempotent, and it should
|
||||
// have thrown again. Since it didn't, rethrow the original error, so
|
||||
// React's internal stack is not misaligned.
|
||||
rethrowOriginalError();
|
||||
}
|
||||
}
|
||||
if (__DEV__ && ReactFiberInstrumentation.debugTool) {
|
||||
ReactFiberInstrumentation.debugTool.onBeginWork(workInProgress);
|
||||
@@ -830,7 +930,7 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
|
||||
nextUnitOfWork === null
|
||||
) {
|
||||
// Reset the stack and start working from the root.
|
||||
resetContextStack();
|
||||
resetStack();
|
||||
nextRoot = root;
|
||||
nextRenderExpirationTime = expirationTime;
|
||||
nextUnitOfWork = createWorkInProgress(
|
||||
@@ -858,13 +958,18 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
|
||||
|
||||
if (__DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback) {
|
||||
const failedUnitOfWork = nextUnitOfWork;
|
||||
replayUnitOfWork(failedUnitOfWork, isAsync);
|
||||
replayUnitOfWork(failedUnitOfWork, thrownValue, isAsync);
|
||||
}
|
||||
|
||||
const sourceFiber: Fiber = nextUnitOfWork;
|
||||
let returnFiber = sourceFiber.return;
|
||||
if (returnFiber === null) {
|
||||
// This is a fatal error.
|
||||
// This is the root. The root could capture its own errors. However,
|
||||
// we don't know if it errors before or after we pushed the host
|
||||
// context. This information is needed to avoid a stack mismatch.
|
||||
// Because we're not sure, treat this as a fatal error. We could track
|
||||
// which phase it fails in, but doesn't seem worth it. At least
|
||||
// for now.
|
||||
didFatal = true;
|
||||
onUncaughtError(thrownValue);
|
||||
break;
|
||||
@@ -876,23 +981,32 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
|
||||
} while (true);
|
||||
|
||||
// We're done performing work. Time to clean up.
|
||||
stopWorkLoopTimer(interruptedBy);
|
||||
interruptedBy = null;
|
||||
let didCompleteRoot = false;
|
||||
isWorking = false;
|
||||
|
||||
// Yield back to main thread.
|
||||
if (didFatal) {
|
||||
stopWorkLoopTimer(interruptedBy, didCompleteRoot);
|
||||
interruptedBy = null;
|
||||
// There was a fatal error.
|
||||
if (__DEV__) {
|
||||
stack.resetStackAfterFatalErrorInDev();
|
||||
}
|
||||
return null;
|
||||
} else if (nextUnitOfWork === null) {
|
||||
// We reached the root.
|
||||
if (isRootReadyForCommit) {
|
||||
didCompleteRoot = true;
|
||||
stopWorkLoopTimer(interruptedBy, didCompleteRoot);
|
||||
interruptedBy = null;
|
||||
// The root successfully completed. It's ready for commit.
|
||||
root.pendingCommitExpirationTime = expirationTime;
|
||||
const finishedWork = root.current.alternate;
|
||||
return finishedWork;
|
||||
} else {
|
||||
// The root did not complete.
|
||||
stopWorkLoopTimer(interruptedBy, didCompleteRoot);
|
||||
interruptedBy = null;
|
||||
invariant(
|
||||
false,
|
||||
'Expired work should have completed. This error is likely caused ' +
|
||||
@@ -900,6 +1014,8 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
|
||||
);
|
||||
}
|
||||
} else {
|
||||
stopWorkLoopTimer(interruptedBy, didCompleteRoot);
|
||||
interruptedBy = null;
|
||||
// There's more work to do, but we ran out of time. Yield back to
|
||||
// the renderer.
|
||||
return null;
|
||||
@@ -1091,9 +1207,17 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
|
||||
) {
|
||||
// This is an interruption. (Used for performance tracking.)
|
||||
interruptedBy = fiber;
|
||||
resetContextStack();
|
||||
resetStack();
|
||||
}
|
||||
if (nextRoot !== root || !isWorking) {
|
||||
if (
|
||||
// If we're in the render phase, we don't need to schedule this root
|
||||
// for an update, because we'll do it before we exit...
|
||||
!isWorking ||
|
||||
isCommitting ||
|
||||
// ...unless this is a different root than the one we're rendering.
|
||||
nextRoot !== root
|
||||
) {
|
||||
// Add this root to the root schedule.
|
||||
requestWork(root, expirationTime);
|
||||
}
|
||||
if (nestedUpdateCount > NESTED_UPDATE_LIMIT) {
|
||||
@@ -1361,7 +1485,8 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
|
||||
|
||||
if (enableUserTimingAPI && deadline !== null) {
|
||||
const didExpire = nextFlushedExpirationTime < recalculateCurrentTime();
|
||||
stopRequestCallbackTimer(didExpire);
|
||||
const timeout = expirationTimeToMs(nextFlushedExpirationTime);
|
||||
stopRequestCallbackTimer(didExpire, timeout);
|
||||
}
|
||||
|
||||
if (isAsync) {
|
||||
@@ -1421,7 +1546,11 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
|
||||
// Perform work on root as if the given expiration time is the current time.
|
||||
// This has the effect of synchronously flushing all work up to and
|
||||
// including the given time.
|
||||
nextFlushedRoot = root;
|
||||
nextFlushedExpirationTime = expirationTime;
|
||||
performWorkOnRoot(root, expirationTime, false);
|
||||
// Flush any sync work that was scheduled by lifecycles
|
||||
performSyncWork();
|
||||
finishRendering();
|
||||
}
|
||||
|
||||
@@ -1679,5 +1808,6 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
|
||||
interactiveUpdates,
|
||||
flushInteractiveUpdates,
|
||||
computeUniqueAsyncExpiration,
|
||||
legacyContext,
|
||||
};
|
||||
}
|
||||
|
||||
127
packages/react-reconciler/src/ReactFiberStack.js
vendored
127
packages/react-reconciler/src/ReactFiberStack.js
vendored
@@ -15,65 +15,54 @@ export type StackCursor<T> = {
|
||||
current: T,
|
||||
};
|
||||
|
||||
const valueStack: Array<any> = [];
|
||||
export type Stack = {
|
||||
createCursor<T>(defaultValue: T): StackCursor<T>,
|
||||
isEmpty(): boolean,
|
||||
push<T>(cursor: StackCursor<T>, value: T, fiber: Fiber): void,
|
||||
pop<T>(cursor: StackCursor<T>, fiber: Fiber): void,
|
||||
|
||||
let fiberStack: Array<Fiber | null>;
|
||||
// DEV only
|
||||
checkThatStackIsEmpty(): void,
|
||||
resetStackAfterFatalErrorInDev(): void,
|
||||
};
|
||||
|
||||
if (__DEV__) {
|
||||
fiberStack = [];
|
||||
}
|
||||
export default function(): Stack {
|
||||
const valueStack: Array<any> = [];
|
||||
|
||||
let index = -1;
|
||||
let fiberStack: Array<Fiber | null>;
|
||||
|
||||
export function createCursor<T>(defaultValue: T): StackCursor<T> {
|
||||
return {
|
||||
current: defaultValue,
|
||||
};
|
||||
}
|
||||
if (__DEV__) {
|
||||
fiberStack = [];
|
||||
}
|
||||
|
||||
export function isEmpty(): boolean {
|
||||
return index === -1;
|
||||
}
|
||||
let index = -1;
|
||||
|
||||
function createCursor<T>(defaultValue: T): StackCursor<T> {
|
||||
return {
|
||||
current: defaultValue,
|
||||
};
|
||||
}
|
||||
|
||||
function isEmpty(): boolean {
|
||||
return index === -1;
|
||||
}
|
||||
|
||||
function pop<T>(cursor: StackCursor<T>, fiber: Fiber): void {
|
||||
if (index < 0) {
|
||||
if (__DEV__) {
|
||||
warning(false, 'Unexpected pop.');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
export function pop<T>(cursor: StackCursor<T>, fiber: Fiber): void {
|
||||
if (index < 0) {
|
||||
if (__DEV__) {
|
||||
warning(false, 'Unexpected pop.');
|
||||
if (fiber !== fiberStack[index]) {
|
||||
warning(false, 'Unexpected Fiber popped.');
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (__DEV__) {
|
||||
if (fiber !== fiberStack[index]) {
|
||||
warning(false, 'Unexpected Fiber popped.');
|
||||
}
|
||||
}
|
||||
cursor.current = valueStack[index];
|
||||
|
||||
cursor.current = valueStack[index];
|
||||
|
||||
valueStack[index] = null;
|
||||
|
||||
if (__DEV__) {
|
||||
fiberStack[index] = null;
|
||||
}
|
||||
|
||||
index--;
|
||||
}
|
||||
|
||||
export function push<T>(cursor: StackCursor<T>, value: T, fiber: Fiber): void {
|
||||
index++;
|
||||
|
||||
valueStack[index] = cursor.current;
|
||||
|
||||
if (__DEV__) {
|
||||
fiberStack[index] = fiber;
|
||||
}
|
||||
|
||||
cursor.current = value;
|
||||
}
|
||||
|
||||
export function reset(): void {
|
||||
while (index > -1) {
|
||||
valueStack[index] = null;
|
||||
|
||||
if (__DEV__) {
|
||||
@@ -82,4 +71,44 @@ export function reset(): void {
|
||||
|
||||
index--;
|
||||
}
|
||||
|
||||
function push<T>(cursor: StackCursor<T>, value: T, fiber: Fiber): void {
|
||||
index++;
|
||||
|
||||
valueStack[index] = cursor.current;
|
||||
|
||||
if (__DEV__) {
|
||||
fiberStack[index] = fiber;
|
||||
}
|
||||
|
||||
cursor.current = value;
|
||||
}
|
||||
|
||||
function checkThatStackIsEmpty() {
|
||||
if (__DEV__) {
|
||||
if (index !== -1) {
|
||||
warning(
|
||||
false,
|
||||
'Expected an empty stack. Something was not reset properly.',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function resetStackAfterFatalErrorInDev() {
|
||||
if (__DEV__) {
|
||||
index = -1;
|
||||
valueStack.length = 0;
|
||||
fiberStack.length = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
createCursor,
|
||||
isEmpty,
|
||||
pop,
|
||||
push,
|
||||
checkThatStackIsEmpty,
|
||||
resetStackAfterFatalErrorInDev,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -4,8 +4,16 @@
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import type {Fiber} from './ReactFiber';
|
||||
import type {ExpirationTime} from './ReactFiberExpirationTime';
|
||||
import type {HostContext} from './ReactFiberHostContext';
|
||||
import type {LegacyContext} from './ReactFiberContext';
|
||||
import type {NewContext} from './ReactFiberNewContext';
|
||||
import type {UpdateQueue} from './ReactFiberUpdateQueue';
|
||||
|
||||
import {createCapturedValue} from './ReactCapturedValue';
|
||||
import {ensureUpdateQueues} from './ReactFiberUpdateQueue';
|
||||
|
||||
@@ -25,14 +33,10 @@ import {
|
||||
|
||||
import {enableGetDerivedStateFromCatch} from 'shared/ReactFeatureFlags';
|
||||
|
||||
import {
|
||||
popContextProvider as popLegacyContextProvider,
|
||||
popTopLevelContextObject as popTopLevelLegacyContextObject,
|
||||
} from './ReactFiberContext';
|
||||
import {popProvider} from './ReactFiberNewContext';
|
||||
|
||||
export default function(
|
||||
export default function<C, CX>(
|
||||
hostContext: HostContext<C, CX>,
|
||||
legacyContext: LegacyContext,
|
||||
newContext: NewContext,
|
||||
scheduleWork: (
|
||||
fiber: Fiber,
|
||||
startTime: ExpirationTime,
|
||||
@@ -41,6 +45,11 @@ export default function(
|
||||
isAlreadyFailedLegacyErrorBoundary: (instance: mixed) => boolean,
|
||||
) {
|
||||
const {popHostContainer, popHostContext} = hostContext;
|
||||
const {
|
||||
popContextProvider: popLegacyContextProvider,
|
||||
popTopLevelContextObject: popTopLevelLegacyContextObject,
|
||||
} = legacyContext;
|
||||
const {popProvider} = newContext;
|
||||
|
||||
function throwException(
|
||||
returnFiber: Fiber,
|
||||
@@ -61,7 +70,9 @@ export default function(
|
||||
// Uncaught error
|
||||
const errorInfo = value;
|
||||
ensureUpdateQueues(workInProgress);
|
||||
const updateQueue: UpdateQueue = (workInProgress.updateQueue: any);
|
||||
const updateQueue: UpdateQueue<
|
||||
any,
|
||||
> = (workInProgress.updateQueue: any);
|
||||
updateQueue.capturedValues = [errorInfo];
|
||||
workInProgress.effectTag |= ShouldCapture;
|
||||
return;
|
||||
@@ -79,7 +90,9 @@ export default function(
|
||||
!isAlreadyFailedLegacyErrorBoundary(instance)))
|
||||
) {
|
||||
ensureUpdateQueues(workInProgress);
|
||||
const updateQueue: UpdateQueue = (workInProgress.updateQueue: any);
|
||||
const updateQueue: UpdateQueue<
|
||||
any,
|
||||
> = (workInProgress.updateQueue: any);
|
||||
const capturedValues = updateQueue.capturedValues;
|
||||
if (capturedValues === null) {
|
||||
updateQueue.capturedValues = [value];
|
||||
@@ -97,7 +110,7 @@ export default function(
|
||||
} while (workInProgress !== null);
|
||||
}
|
||||
|
||||
function unwindWork(workInProgress) {
|
||||
function unwindWork(workInProgress: Fiber) {
|
||||
switch (workInProgress.tag) {
|
||||
case ClassComponent: {
|
||||
popLegacyContextProvider(workInProgress);
|
||||
@@ -132,8 +145,36 @@ export default function(
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function unwindInterruptedWork(interruptedWork: Fiber) {
|
||||
switch (interruptedWork.tag) {
|
||||
case ClassComponent: {
|
||||
popLegacyContextProvider(interruptedWork);
|
||||
break;
|
||||
}
|
||||
case HostRoot: {
|
||||
popHostContainer(interruptedWork);
|
||||
popTopLevelLegacyContextObject(interruptedWork);
|
||||
break;
|
||||
}
|
||||
case HostComponent: {
|
||||
popHostContext(interruptedWork);
|
||||
break;
|
||||
}
|
||||
case HostPortal:
|
||||
popHostContainer(interruptedWork);
|
||||
break;
|
||||
case ContextProvider:
|
||||
popProvider(interruptedWork);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
throwException,
|
||||
unwindWork,
|
||||
unwindInterruptedWork,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -213,7 +213,10 @@ if (__DEV__) {
|
||||
) {
|
||||
pendingComponentWillReceivePropsWarnings.push(fiber);
|
||||
}
|
||||
if (typeof instance.componentWillUpdate === 'function') {
|
||||
if (
|
||||
typeof instance.componentWillUpdate === 'function' &&
|
||||
instance.componentWillUpdate.__suppressDeprecationWarning !== true
|
||||
) {
|
||||
pendingComponentWillUpdateWarnings.push(fiber);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -36,6 +36,10 @@ describe('ReactIncrementalErrorHandling', () => {
|
||||
return {type: 'span', children: [], prop};
|
||||
}
|
||||
|
||||
function normalizeCodeLocInfo(str) {
|
||||
return str && str.replace(/\(at .+?:\d+\)/g, '(at **)');
|
||||
}
|
||||
|
||||
it('recovers from errors asynchronously', () => {
|
||||
class ErrorBoundary extends React.Component {
|
||||
state = {error: null};
|
||||
@@ -1302,4 +1306,74 @@ describe('ReactIncrementalErrorHandling', () => {
|
||||
]);
|
||||
expect(ReactNoop.getChildren()).toEqual([span('Caught an error: oops!')]);
|
||||
});
|
||||
|
||||
it('provides component stack to the error boundary with componentDidCatch', () => {
|
||||
class ErrorBoundary extends React.Component {
|
||||
state = {error: null, errorInfo: null};
|
||||
componentDidCatch(error, errorInfo) {
|
||||
this.setState({error, errorInfo});
|
||||
}
|
||||
render() {
|
||||
if (this.state.errorInfo) {
|
||||
ReactNoop.yield('render error message');
|
||||
return (
|
||||
<span
|
||||
prop={`Caught an error:${normalizeCodeLocInfo(
|
||||
this.state.errorInfo.componentStack,
|
||||
)}.`}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
|
||||
function BrokenRender(props) {
|
||||
throw new Error('Hello');
|
||||
}
|
||||
|
||||
ReactNoop.render(
|
||||
<ErrorBoundary>
|
||||
<BrokenRender />
|
||||
</ErrorBoundary>,
|
||||
);
|
||||
ReactNoop.flushDeferredPri();
|
||||
expect(ReactNoop.getChildren()).toEqual([
|
||||
span(
|
||||
'Caught an error:\n' +
|
||||
(__DEV__
|
||||
? ' in BrokenRender (at **)\n'
|
||||
: ' in BrokenRender\n') +
|
||||
(__DEV__ ? ' in ErrorBoundary (at **).' : ' in ErrorBoundary.'),
|
||||
),
|
||||
]);
|
||||
});
|
||||
|
||||
it('does not provide component stack to the error boundary with getDerivedStateFromCatch', () => {
|
||||
class ErrorBoundary extends React.Component {
|
||||
state = {error: null};
|
||||
static getDerivedStateFromCatch(error, errorInfo) {
|
||||
expect(errorInfo).toBeUndefined();
|
||||
return {error};
|
||||
}
|
||||
render() {
|
||||
if (this.state.error) {
|
||||
return <span prop={`Caught an error: ${this.state.error.message}`} />;
|
||||
}
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
|
||||
function BrokenRender(props) {
|
||||
throw new Error('Hello');
|
||||
}
|
||||
|
||||
ReactNoop.render(
|
||||
<ErrorBoundary>
|
||||
<BrokenRender />
|
||||
</ErrorBoundary>,
|
||||
);
|
||||
ReactNoop.flushDeferredPri();
|
||||
expect(ReactNoop.getChildren()).toEqual([span('Caught an error: Hello')]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* Copyright (c) 2013-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @jest-environment node
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
describe('ReactIncrementalErrorReplay-test', () => {
|
||||
it('copies all keys when stashing potentially failing work', () => {
|
||||
// Note: this test is fragile and relies on internals.
|
||||
// We almost always try to avoid such tests, but here the cost of
|
||||
// the list getting out of sync (and causing subtle bugs in rare cases)
|
||||
// is higher than the cost of maintaing the test.
|
||||
const {
|
||||
// Any Fiber factory function will do.
|
||||
createHostRootFiber,
|
||||
// This is the method we're going to test.
|
||||
// If this is no longer used, you can delete this test file.
|
||||
assignFiberPropertiesInDEV,
|
||||
} = require('../ReactFiber');
|
||||
|
||||
// Get a real fiber.
|
||||
const realFiber = createHostRootFiber(false);
|
||||
const stash = assignFiberPropertiesInDEV(null, realFiber);
|
||||
|
||||
// Verify we get all the same fields.
|
||||
expect(realFiber).toEqual(stash);
|
||||
|
||||
// Mutate the original.
|
||||
for (let key in realFiber) {
|
||||
realFiber[key] = key + '_' + Math.random();
|
||||
}
|
||||
expect(realFiber).not.toEqual(stash);
|
||||
|
||||
// Verify we can still "revert" to the stashed properties.
|
||||
expect(assignFiberPropertiesInDEV(realFiber, stash)).toBe(realFiber);
|
||||
expect(realFiber).toEqual(stash);
|
||||
});
|
||||
});
|
||||
59
packages/react-reconciler/src/__tests__/ReactIncrementalErrorReplay-test.js
vendored
Normal file
59
packages/react-reconciler/src/__tests__/ReactIncrementalErrorReplay-test.js
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* Copyright (c) 2013-present, Facebook, Inc.
|
||||
*
|
||||
* 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
|
||||
* @jest-environment node
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
let React;
|
||||
let ReactNoop;
|
||||
|
||||
describe('ReactIncrementalErrorReplay', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
React = require('react');
|
||||
ReactNoop = require('react-noop-renderer');
|
||||
});
|
||||
|
||||
function div(...children) {
|
||||
children = children.map(c => (typeof c === 'string' ? {text: c} : c));
|
||||
return {type: 'div', children, prop: undefined};
|
||||
}
|
||||
|
||||
function span(prop) {
|
||||
return {type: 'span', children: [], prop};
|
||||
}
|
||||
|
||||
it('should fail gracefully on error in the host environment', () => {
|
||||
ReactNoop.simulateErrorInHostConfig(() => {
|
||||
ReactNoop.render(<span />);
|
||||
expect(() => ReactNoop.flush()).toThrow('Error in host config.');
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail gracefully on error that does not reproduce on replay', () => {
|
||||
let didInit = false;
|
||||
|
||||
function badLazyInit() {
|
||||
const needsInit = !didInit;
|
||||
didInit = true;
|
||||
if (needsInit) {
|
||||
throw new Error('Hi');
|
||||
}
|
||||
}
|
||||
|
||||
class App extends React.Component {
|
||||
render() {
|
||||
badLazyInit();
|
||||
return <div />;
|
||||
}
|
||||
}
|
||||
ReactNoop.render(<App />);
|
||||
expect(() => ReactNoop.flush()).toThrow('Hi');
|
||||
});
|
||||
});
|
||||
@@ -542,6 +542,13 @@ ${formatActions(actions)}
|
||||
['c', step(2)],
|
||||
['b', interrupt()],
|
||||
);
|
||||
|
||||
simulateMultipleRoots(
|
||||
['c', toggle(0)],
|
||||
['c', step(1)],
|
||||
['b', flush(7)],
|
||||
['c', toggle(0)],
|
||||
);
|
||||
});
|
||||
|
||||
it('generative tests', () => {
|
||||
|
||||
@@ -373,4 +373,52 @@ describe('ReactIncrementalUpdates', () => {
|
||||
});
|
||||
ReactNoop.flush();
|
||||
});
|
||||
|
||||
it('getDerivedStateFromProps should update base state of updateQueue (based on product bug)', () => {
|
||||
// Based on real-world bug.
|
||||
|
||||
let foo;
|
||||
class Foo extends React.Component {
|
||||
state = {value: 'initial state'};
|
||||
static getDerivedStateFromProps() {
|
||||
return {value: 'derived state'};
|
||||
}
|
||||
render() {
|
||||
foo = this;
|
||||
return (
|
||||
<React.Fragment>
|
||||
<span prop={this.state.value} />
|
||||
<Bar />
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let bar;
|
||||
class Bar extends React.Component {
|
||||
render() {
|
||||
bar = this;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
ReactNoop.flushSync(() => {
|
||||
ReactNoop.render(<Foo />);
|
||||
});
|
||||
expect(ReactNoop.getChildren()).toEqual([span('derived state')]);
|
||||
|
||||
ReactNoop.flushSync(() => {
|
||||
// Triggers getDerivedStateFromProps again
|
||||
ReactNoop.render(<Foo />);
|
||||
// The noop callback is needed to trigger the specific internal path that
|
||||
// led to this bug. Removing it causes it to "accidentally" work.
|
||||
foo.setState({value: 'update state'}, function noop() {});
|
||||
});
|
||||
expect(ReactNoop.getChildren()).toEqual([span('derived state')]);
|
||||
|
||||
ReactNoop.flushSync(() => {
|
||||
bar.setState({});
|
||||
});
|
||||
expect(ReactNoop.getChildren()).toEqual([span('derived state')]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -727,39 +727,146 @@ describe('ReactNewContext', () => {
|
||||
expect(ReactNoop.getChildren()).toEqual([span('Child')]);
|
||||
});
|
||||
|
||||
it('consumer bails out if children and value are unchanged (like sCU)', () => {
|
||||
it('consumer bails out if value is unchanged and something above bailed out', () => {
|
||||
const Context = React.createContext(0);
|
||||
|
||||
function Child() {
|
||||
ReactNoop.yield('Child');
|
||||
return <span prop="Child" />;
|
||||
function renderChildValue(value) {
|
||||
ReactNoop.yield('Consumer');
|
||||
return <span prop={value} />;
|
||||
}
|
||||
|
||||
function renderConsumer(context) {
|
||||
return <Child context={context} />;
|
||||
}
|
||||
|
||||
function App(props) {
|
||||
ReactNoop.yield('App');
|
||||
function ChildWithInlineRenderCallback() {
|
||||
ReactNoop.yield('ChildWithInlineRenderCallback');
|
||||
// Note: we are intentionally passing an inline arrow. Don't refactor.
|
||||
return (
|
||||
<Context.Provider value={props.value}>
|
||||
<Context.Consumer>{renderConsumer}</Context.Consumer>
|
||||
</Context.Provider>
|
||||
<Context.Consumer>{value => renderChildValue(value)}</Context.Consumer>
|
||||
);
|
||||
}
|
||||
|
||||
function ChildWithCachedRenderCallback() {
|
||||
ReactNoop.yield('ChildWithCachedRenderCallback');
|
||||
return <Context.Consumer>{renderChildValue}</Context.Consumer>;
|
||||
}
|
||||
|
||||
class PureIndirection extends React.PureComponent {
|
||||
render() {
|
||||
ReactNoop.yield('PureIndirection');
|
||||
return (
|
||||
<React.Fragment>
|
||||
<ChildWithInlineRenderCallback />
|
||||
<ChildWithCachedRenderCallback />
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class App extends React.Component {
|
||||
render() {
|
||||
ReactNoop.yield('App');
|
||||
return (
|
||||
<Context.Provider value={this.props.value}>
|
||||
<PureIndirection />
|
||||
</Context.Provider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Initial mount
|
||||
ReactNoop.render(<App value={1} />);
|
||||
expect(ReactNoop.flush()).toEqual(['App', 'Child']);
|
||||
expect(ReactNoop.getChildren()).toEqual([span('Child')]);
|
||||
|
||||
// Update
|
||||
ReactNoop.render(<App value={1} />);
|
||||
expect(ReactNoop.flush()).toEqual([
|
||||
'App',
|
||||
// Child does not re-render
|
||||
'PureIndirection',
|
||||
'ChildWithInlineRenderCallback',
|
||||
'Consumer',
|
||||
'ChildWithCachedRenderCallback',
|
||||
'Consumer',
|
||||
]);
|
||||
expect(ReactNoop.getChildren()).toEqual([span('Child')]);
|
||||
expect(ReactNoop.getChildren()).toEqual([span(1), span(1)]);
|
||||
|
||||
// Update (bailout)
|
||||
ReactNoop.render(<App value={1} />);
|
||||
expect(ReactNoop.flush()).toEqual(['App']);
|
||||
expect(ReactNoop.getChildren()).toEqual([span(1), span(1)]);
|
||||
|
||||
// Update (no bailout)
|
||||
ReactNoop.render(<App value={2} />);
|
||||
expect(ReactNoop.flush()).toEqual(['App', 'Consumer', 'Consumer']);
|
||||
expect(ReactNoop.getChildren()).toEqual([span(2), span(2)]);
|
||||
});
|
||||
|
||||
// Context consumer bails out on propagating "deep" updates when `value` hasn't changed.
|
||||
// However, it doesn't bail out from rendering if the component above it re-rendered anyway.
|
||||
// If we bailed out on referential equality, it would be confusing that you
|
||||
// can call this.setState(), but an autobound render callback "blocked" the update.
|
||||
// https://github.com/facebook/react/pull/12470#issuecomment-376917711
|
||||
it('consumer does not bail out if there were no bailouts above it', () => {
|
||||
const Context = React.createContext(0);
|
||||
|
||||
class App extends React.Component {
|
||||
state = {
|
||||
text: 'hello',
|
||||
};
|
||||
|
||||
renderConsumer = context => {
|
||||
ReactNoop.yield('App#renderConsumer');
|
||||
return <span prop={this.state.text} />;
|
||||
};
|
||||
|
||||
render() {
|
||||
ReactNoop.yield('App');
|
||||
return (
|
||||
<Context.Provider value={this.props.value}>
|
||||
<Context.Consumer>{this.renderConsumer}</Context.Consumer>
|
||||
</Context.Provider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Initial mount
|
||||
let inst;
|
||||
ReactNoop.render(<App value={1} ref={ref => (inst = ref)} />);
|
||||
expect(ReactNoop.flush()).toEqual(['App', 'App#renderConsumer']);
|
||||
expect(ReactNoop.getChildren()).toEqual([span('hello')]);
|
||||
|
||||
// Update
|
||||
inst.setState({text: 'goodbye'});
|
||||
expect(ReactNoop.flush()).toEqual(['App', 'App#renderConsumer']);
|
||||
expect(ReactNoop.getChildren()).toEqual([span('goodbye')]);
|
||||
});
|
||||
|
||||
// This is a regression case for https://github.com/facebook/react/issues/12389.
|
||||
it('does not run into an infinite loop', () => {
|
||||
const Context = React.createContext(null);
|
||||
|
||||
class App extends React.Component {
|
||||
renderItem(id) {
|
||||
return (
|
||||
<span key={id}>
|
||||
<Context.Consumer>{() => <span>inner</span>}</Context.Consumer>
|
||||
<span>outer</span>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
renderList() {
|
||||
const list = [1, 2].map(id => this.renderItem(id));
|
||||
if (this.props.reverse) {
|
||||
list.reverse();
|
||||
}
|
||||
return list;
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<Context.Provider value={{}}>{this.renderList()}</Context.Provider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ReactNoop.render(<App reverse={false} />);
|
||||
ReactNoop.flush();
|
||||
ReactNoop.render(<App reverse={true} />);
|
||||
ReactNoop.flush();
|
||||
ReactNoop.render(<App reverse={false} />);
|
||||
ReactNoop.flush();
|
||||
});
|
||||
|
||||
describe('fuzz test', () => {
|
||||
|
||||
@@ -1,23 +1,24 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`ReactDebugFiberPerf captures all lifecycles 1`] = `
|
||||
"⚛ (Waiting for async callback...)
|
||||
"⚛ (Waiting for async callback... will force flush in 5230 ms)
|
||||
|
||||
// Mount
|
||||
⚛ (React Tree Reconciliation)
|
||||
⚛ (React Tree Reconciliation: Completed Root)
|
||||
⚛ AllLifecycles [mount]
|
||||
⚛ AllLifecycles.componentWillMount
|
||||
⚛ AllLifecycles.getChildContext
|
||||
|
||||
⚛ (Committing Changes)
|
||||
⚛ (Committing Snapshot Effects: 0 Total)
|
||||
⚛ (Committing Host Effects: 1 Total)
|
||||
⚛ (Calling Lifecycle Methods: 1 Total)
|
||||
⚛ AllLifecycles.componentDidMount
|
||||
|
||||
⚛ (Waiting for async callback...)
|
||||
⚛ (Waiting for async callback... will force flush in 5230 ms)
|
||||
|
||||
// Update
|
||||
⚛ (React Tree Reconciliation)
|
||||
⚛ (React Tree Reconciliation: Completed Root)
|
||||
⚛ AllLifecycles [update]
|
||||
⚛ AllLifecycles.componentWillReceiveProps
|
||||
⚛ AllLifecycles.shouldComponentUpdate
|
||||
@@ -25,16 +26,18 @@ exports[`ReactDebugFiberPerf captures all lifecycles 1`] = `
|
||||
⚛ AllLifecycles.getChildContext
|
||||
|
||||
⚛ (Committing Changes)
|
||||
⚛ (Committing Snapshot Effects: 0 Total)
|
||||
⚛ (Committing Host Effects: 2 Total)
|
||||
⚛ (Calling Lifecycle Methods: 2 Total)
|
||||
⚛ AllLifecycles.componentDidUpdate
|
||||
|
||||
⚛ (Waiting for async callback...)
|
||||
⚛ (Waiting for async callback... will force flush in 5230 ms)
|
||||
|
||||
// Unmount
|
||||
⚛ (React Tree Reconciliation)
|
||||
⚛ (React Tree Reconciliation: Completed Root)
|
||||
|
||||
⚛ (Committing Changes)
|
||||
⚛ (Committing Snapshot Effects: 0 Total)
|
||||
⚛ (Committing Host Effects: 1 Total)
|
||||
⚛ AllLifecycles.componentWillUnmount
|
||||
⚛ (Calling Lifecycle Methods: 0 Total)
|
||||
@@ -42,10 +45,10 @@ exports[`ReactDebugFiberPerf captures all lifecycles 1`] = `
|
||||
`;
|
||||
|
||||
exports[`ReactDebugFiberPerf deduplicates lifecycle names during commit to reduce overhead 1`] = `
|
||||
"⚛ (Waiting for async callback...)
|
||||
"⚛ (Waiting for async callback... will force flush in 5230 ms)
|
||||
|
||||
// The commit phase should mention A and B just once
|
||||
⚛ (React Tree Reconciliation)
|
||||
⚛ (React Tree Reconciliation: Completed Root)
|
||||
⚛ Parent [update]
|
||||
⚛ A [update]
|
||||
⚛ B [update]
|
||||
@@ -53,16 +56,17 @@ exports[`ReactDebugFiberPerf deduplicates lifecycle names during commit to reduc
|
||||
⚛ B [update]
|
||||
|
||||
⚛ (Committing Changes)
|
||||
⚛ (Committing Snapshot Effects: 0 Total)
|
||||
⚛ (Committing Host Effects: 9 Total)
|
||||
⚛ (Calling Lifecycle Methods: 9 Total)
|
||||
⚛ A.componentDidUpdate
|
||||
⚛ B.componentDidUpdate
|
||||
|
||||
⚛ (Waiting for async callback...)
|
||||
⚛ (Waiting for async callback... will force flush in 5230 ms)
|
||||
|
||||
// Because of deduplication, we don't know B was cascading,
|
||||
// but we should still see the warning for the commit phase.
|
||||
⚛ (React Tree Reconciliation)
|
||||
⚛ (React Tree Reconciliation: Completed Root)
|
||||
⚛ Parent [update]
|
||||
⚛ A [update]
|
||||
⚛ B [update]
|
||||
@@ -70,15 +74,17 @@ exports[`ReactDebugFiberPerf deduplicates lifecycle names during commit to reduc
|
||||
⚛ B [update]
|
||||
|
||||
⛔ (Committing Changes) Warning: Lifecycle hook scheduled a cascading update
|
||||
⚛ (Committing Snapshot Effects: 0 Total)
|
||||
⚛ (Committing Host Effects: 9 Total)
|
||||
⚛ (Calling Lifecycle Methods: 9 Total)
|
||||
⚛ A.componentDidUpdate
|
||||
⚛ B.componentDidUpdate
|
||||
|
||||
⚛ (React Tree Reconciliation)
|
||||
⚛ (React Tree Reconciliation: Completed Root)
|
||||
⚛ B [update]
|
||||
|
||||
⚛ (Committing Changes)
|
||||
⚛ (Committing Snapshot Effects: 0 Total)
|
||||
⚛ (Committing Host Effects: 3 Total)
|
||||
⚛ (Calling Lifecycle Methods: 3 Total)
|
||||
⚛ B.componentDidUpdate
|
||||
@@ -86,109 +92,117 @@ exports[`ReactDebugFiberPerf deduplicates lifecycle names during commit to reduc
|
||||
`;
|
||||
|
||||
exports[`ReactDebugFiberPerf does not schedule an extra callback if setState is called during a synchronous commit phase 1`] = `
|
||||
"⚛ (React Tree Reconciliation)
|
||||
"⚛ (React Tree Reconciliation: Completed Root)
|
||||
⚛ Component [mount]
|
||||
|
||||
⛔ (Committing Changes) Warning: Lifecycle hook scheduled a cascading update
|
||||
⚛ (Committing Snapshot Effects: 0 Total)
|
||||
⚛ (Committing Host Effects: 1 Total)
|
||||
⚛ (Calling Lifecycle Methods: 1 Total)
|
||||
⛔ Component.componentDidMount Warning: Scheduled a cascading update
|
||||
|
||||
⚛ (React Tree Reconciliation)
|
||||
⚛ (React Tree Reconciliation: Completed Root)
|
||||
⚛ Component [update]
|
||||
|
||||
⚛ (Committing Changes)
|
||||
⚛ (Committing Snapshot Effects: 0 Total)
|
||||
⚛ (Committing Host Effects: 1 Total)
|
||||
⚛ (Calling Lifecycle Methods: 1 Total)
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`ReactDebugFiberPerf does not treat setState from cWM or cWRP as cascading 1`] = `
|
||||
"⚛ (Waiting for async callback...)
|
||||
"⚛ (Waiting for async callback... will force flush in 5230 ms)
|
||||
|
||||
// Should not print a warning
|
||||
⚛ (React Tree Reconciliation)
|
||||
⚛ (React Tree Reconciliation: Completed Root)
|
||||
⚛ Parent [mount]
|
||||
⚛ NotCascading [mount]
|
||||
⚛ NotCascading.componentWillMount
|
||||
|
||||
⚛ (Committing Changes)
|
||||
⚛ (Committing Snapshot Effects: 0 Total)
|
||||
⚛ (Committing Host Effects: 1 Total)
|
||||
⚛ (Calling Lifecycle Methods: 0 Total)
|
||||
|
||||
⚛ (Waiting for async callback...)
|
||||
⚛ (Waiting for async callback... will force flush in 5230 ms)
|
||||
|
||||
// Should not print a warning
|
||||
⚛ (React Tree Reconciliation)
|
||||
⚛ (React Tree Reconciliation: Completed Root)
|
||||
⚛ Parent [update]
|
||||
⚛ NotCascading [update]
|
||||
⚛ NotCascading.componentWillReceiveProps
|
||||
|
||||
⚛ (Committing Changes)
|
||||
⚛ (Committing Snapshot Effects: 0 Total)
|
||||
⚛ (Committing Host Effects: 2 Total)
|
||||
⚛ (Calling Lifecycle Methods: 2 Total)
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`ReactDebugFiberPerf measures a simple reconciliation 1`] = `
|
||||
"⚛ (Waiting for async callback...)
|
||||
"⚛ (Waiting for async callback... will force flush in 5230 ms)
|
||||
|
||||
// Mount
|
||||
⚛ (React Tree Reconciliation)
|
||||
⚛ (React Tree Reconciliation: Completed Root)
|
||||
⚛ Parent [mount]
|
||||
⚛ Child [mount]
|
||||
|
||||
⚛ (Committing Changes)
|
||||
⚛ (Committing Snapshot Effects: 0 Total)
|
||||
⚛ (Committing Host Effects: 1 Total)
|
||||
⚛ (Calling Lifecycle Methods: 0 Total)
|
||||
|
||||
⚛ (Waiting for async callback...)
|
||||
⚛ (Waiting for async callback... will force flush in 5230 ms)
|
||||
|
||||
// Update
|
||||
⚛ (React Tree Reconciliation)
|
||||
⚛ (React Tree Reconciliation: Completed Root)
|
||||
⚛ Parent [update]
|
||||
⚛ Child [update]
|
||||
|
||||
⚛ (Committing Changes)
|
||||
⚛ (Committing Snapshot Effects: 0 Total)
|
||||
⚛ (Committing Host Effects: 2 Total)
|
||||
⚛ (Calling Lifecycle Methods: 2 Total)
|
||||
|
||||
⚛ (Waiting for async callback...)
|
||||
⚛ (Waiting for async callback... will force flush in 5230 ms)
|
||||
|
||||
// Unmount
|
||||
⚛ (React Tree Reconciliation)
|
||||
⚛ (React Tree Reconciliation: Completed Root)
|
||||
|
||||
⚛ (Committing Changes)
|
||||
⚛ (Committing Snapshot Effects: 0 Total)
|
||||
⚛ (Committing Host Effects: 1 Total)
|
||||
⚛ (Calling Lifecycle Methods: 0 Total)
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`ReactDebugFiberPerf measures deferred work in chunks 1`] = `
|
||||
"⚛ (Waiting for async callback...)
|
||||
"⚛ (Waiting for async callback... will force flush in 5230 ms)
|
||||
|
||||
// Start mounting Parent and A
|
||||
⚛ (React Tree Reconciliation)
|
||||
⚛ (React Tree Reconciliation: Yielded)
|
||||
⚛ Parent [mount]
|
||||
⚛ A [mount]
|
||||
⚛ Child [mount]
|
||||
|
||||
⚛ (Waiting for async callback...)
|
||||
⚛ (Waiting for async callback... will force flush in 5230 ms)
|
||||
|
||||
// Mount B just a little (but not enough to memoize)
|
||||
⚛ (React Tree Reconciliation)
|
||||
⚛ (React Tree Reconciliation: Yielded)
|
||||
⚛ Parent [mount]
|
||||
⚛ B [mount]
|
||||
|
||||
⚛ (Waiting for async callback...)
|
||||
⚛ (Waiting for async callback... will force flush in 5230 ms)
|
||||
|
||||
// Complete B and Parent
|
||||
⚛ (React Tree Reconciliation)
|
||||
⚛ (React Tree Reconciliation: Completed Root)
|
||||
⚛ Parent [mount]
|
||||
⚛ B [mount]
|
||||
⚛ Child [mount]
|
||||
|
||||
⚛ (Committing Changes)
|
||||
⚛ (Committing Snapshot Effects: 0 Total)
|
||||
⚛ (Committing Host Effects: 1 Total)
|
||||
⚛ (Calling Lifecycle Methods: 0 Total)
|
||||
"
|
||||
@@ -196,111 +210,119 @@ exports[`ReactDebugFiberPerf measures deferred work in chunks 1`] = `
|
||||
|
||||
exports[`ReactDebugFiberPerf measures deprioritized work 1`] = `
|
||||
"// Flush the parent
|
||||
⚛ (React Tree Reconciliation)
|
||||
⚛ (React Tree Reconciliation: Completed Root)
|
||||
⚛ Parent [mount]
|
||||
|
||||
⚛ (Committing Changes)
|
||||
⚛ (Committing Snapshot Effects: 0 Total)
|
||||
⚛ (Committing Host Effects: 1 Total)
|
||||
⚛ (Calling Lifecycle Methods: 0 Total)
|
||||
|
||||
⚛ (Waiting for async callback...)
|
||||
⚛ (Waiting for async callback... will force flush in 10737418210 ms)
|
||||
|
||||
// Flush the child
|
||||
⚛ (React Tree Reconciliation)
|
||||
⚛ (React Tree Reconciliation: Completed Root)
|
||||
⚛ Child [mount]
|
||||
|
||||
⚛ (Committing Changes)
|
||||
⚛ (Committing Snapshot Effects: 0 Total)
|
||||
⚛ (Committing Host Effects: 3 Total)
|
||||
⚛ (Calling Lifecycle Methods: 2 Total)
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`ReactDebugFiberPerf recovers from caught errors 1`] = `
|
||||
"⚛ (Waiting for async callback...)
|
||||
"⚛ (Waiting for async callback... will force flush in 5230 ms)
|
||||
|
||||
// Stop on Baddie and restart from Boundary
|
||||
⚛ (React Tree Reconciliation)
|
||||
⚛ (React Tree Reconciliation: Yielded)
|
||||
⚛ Parent [mount]
|
||||
⛔ Boundary [mount] Warning: An error was thrown inside this error boundary
|
||||
⚛ Parent [mount]
|
||||
⚛ Baddie [mount]
|
||||
|
||||
⚛ (React Tree Reconciliation)
|
||||
⚛ (React Tree Reconciliation: Completed Root)
|
||||
⚛ Parent [mount]
|
||||
⚛ Boundary [mount]
|
||||
|
||||
⛔ (Committing Changes) Warning: Lifecycle hook scheduled a cascading update
|
||||
⚛ (Committing Snapshot Effects: 0 Total)
|
||||
⚛ (Committing Host Effects: 2 Total)
|
||||
⚛ (Calling Lifecycle Methods: 0 Total)
|
||||
|
||||
⚛ (React Tree Reconciliation)
|
||||
⚛ (React Tree Reconciliation: Completed Root)
|
||||
⚛ Boundary [update]
|
||||
⚛ ErrorReport [mount]
|
||||
|
||||
⚛ (Committing Changes)
|
||||
⚛ (Committing Snapshot Effects: 0 Total)
|
||||
⚛ (Committing Host Effects: 2 Total)
|
||||
⚛ (Calling Lifecycle Methods: 1 Total)
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`ReactDebugFiberPerf recovers from fatal errors 1`] = `
|
||||
"⚛ (Waiting for async callback...)
|
||||
"⚛ (Waiting for async callback... will force flush in 5230 ms)
|
||||
|
||||
// Will fatal
|
||||
⚛ (React Tree Reconciliation)
|
||||
⚛ (React Tree Reconciliation: Yielded)
|
||||
⚛ Parent [mount]
|
||||
⚛ Baddie [mount]
|
||||
|
||||
⚛ (React Tree Reconciliation)
|
||||
⚛ (React Tree Reconciliation: Completed Root)
|
||||
|
||||
⚛ (Committing Changes)
|
||||
⚛ (Committing Snapshot Effects: 0 Total)
|
||||
⚛ (Committing Host Effects: 1 Total)
|
||||
⚛ (Calling Lifecycle Methods: 0 Total)
|
||||
|
||||
⚛ (Waiting for async callback...)
|
||||
⚛ (Waiting for async callback... will force flush in 5230 ms)
|
||||
|
||||
// Will reconcile from a clean state
|
||||
⚛ (React Tree Reconciliation)
|
||||
⚛ (React Tree Reconciliation: Completed Root)
|
||||
⚛ Parent [mount]
|
||||
⚛ Child [mount]
|
||||
|
||||
⚛ (Committing Changes)
|
||||
⚛ (Committing Snapshot Effects: 0 Total)
|
||||
⚛ (Committing Host Effects: 1 Total)
|
||||
⚛ (Calling Lifecycle Methods: 0 Total)
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`ReactDebugFiberPerf skips parents during setState 1`] = `
|
||||
"⚛ (Waiting for async callback...)
|
||||
"⚛ (Waiting for async callback... will force flush in 5230 ms)
|
||||
|
||||
// Should include just A and B, no Parents
|
||||
⚛ (React Tree Reconciliation)
|
||||
⚛ (React Tree Reconciliation: Completed Root)
|
||||
⚛ A [update]
|
||||
⚛ B [update]
|
||||
|
||||
⚛ (Committing Changes)
|
||||
⚛ (Committing Snapshot Effects: 0 Total)
|
||||
⚛ (Committing Host Effects: 6 Total)
|
||||
⚛ (Calling Lifecycle Methods: 6 Total)
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`ReactDebugFiberPerf supports portals 1`] = `
|
||||
"⚛ (Waiting for async callback...)
|
||||
"⚛ (Waiting for async callback... will force flush in 5230 ms)
|
||||
|
||||
⚛ (React Tree Reconciliation)
|
||||
⚛ (React Tree Reconciliation: Completed Root)
|
||||
⚛ Parent [mount]
|
||||
⚛ Child [mount]
|
||||
|
||||
⚛ (Committing Changes)
|
||||
⚛ (Committing Snapshot Effects: 0 Total)
|
||||
⚛ (Committing Host Effects: 2 Total)
|
||||
⚛ (Calling Lifecycle Methods: 0 Total)
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`ReactDebugFiberPerf supports returns 1`] = `
|
||||
"⚛ (Waiting for async callback...)
|
||||
"⚛ (Waiting for async callback... will force flush in 5230 ms)
|
||||
|
||||
⚛ (React Tree Reconciliation)
|
||||
⚛ (React Tree Reconciliation: Completed Root)
|
||||
⚛ App [mount]
|
||||
⚛ CoParent [mount]
|
||||
⚛ Indirection [mount]
|
||||
@@ -310,83 +332,91 @@ exports[`ReactDebugFiberPerf supports returns 1`] = `
|
||||
⚛ Continuation [mount]
|
||||
|
||||
⚛ (Committing Changes)
|
||||
⚛ (Committing Snapshot Effects: 0 Total)
|
||||
⚛ (Committing Host Effects: 3 Total)
|
||||
⚛ (Calling Lifecycle Methods: 0 Total)
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`ReactDebugFiberPerf warns if an in-progress update is interrupted 1`] = `
|
||||
"⚛ (Waiting for async callback...)
|
||||
"⚛ (Waiting for async callback... will force flush in 5230 ms)
|
||||
|
||||
⚛ (React Tree Reconciliation)
|
||||
⚛ (React Tree Reconciliation: Yielded)
|
||||
⚛ Foo [mount]
|
||||
|
||||
⚛ (Waiting for async callback...)
|
||||
⛔ (React Tree Reconciliation) Warning: A top-level update interrupted the previous render
|
||||
⚛ (Waiting for async callback... will force flush in 5230 ms)
|
||||
⛔ (React Tree Reconciliation: Completed Root) Warning: A top-level update interrupted the previous render
|
||||
⚛ Foo [mount]
|
||||
⚛ (Committing Changes)
|
||||
⚛ (Committing Snapshot Effects: 0 Total)
|
||||
⚛ (Committing Host Effects: 1 Total)
|
||||
⚛ (Calling Lifecycle Methods: 0 Total)
|
||||
|
||||
⚛ (React Tree Reconciliation)
|
||||
⚛ (React Tree Reconciliation: Completed Root)
|
||||
|
||||
⚛ (Committing Changes)
|
||||
⚛ (Committing Snapshot Effects: 0 Total)
|
||||
⚛ (Committing Host Effects: 1 Total)
|
||||
⚛ (Calling Lifecycle Methods: 1 Total)
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`ReactDebugFiberPerf warns if async work expires (starvation) 1`] = `
|
||||
"⛔ (Waiting for async callback...) Warning: React was blocked by main thread
|
||||
"⛔ (Waiting for async callback... will force flush in 5230 ms) Warning: React was blocked by main thread
|
||||
|
||||
⚛ (React Tree Reconciliation)
|
||||
⚛ (React Tree Reconciliation: Completed Root)
|
||||
⚛ Foo [mount]
|
||||
|
||||
⚛ (Committing Changes)
|
||||
⚛ (Committing Snapshot Effects: 0 Total)
|
||||
⚛ (Committing Host Effects: 1 Total)
|
||||
⚛ (Calling Lifecycle Methods: 0 Total)
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`ReactDebugFiberPerf warns on cascading renders from setState 1`] = `
|
||||
"⚛ (Waiting for async callback...)
|
||||
"⚛ (Waiting for async callback... will force flush in 5230 ms)
|
||||
|
||||
// Should print a warning
|
||||
⚛ (React Tree Reconciliation)
|
||||
⚛ (React Tree Reconciliation: Completed Root)
|
||||
⚛ Parent [mount]
|
||||
⚛ Cascading [mount]
|
||||
|
||||
⛔ (Committing Changes) Warning: Lifecycle hook scheduled a cascading update
|
||||
⚛ (Committing Snapshot Effects: 0 Total)
|
||||
⚛ (Committing Host Effects: 2 Total)
|
||||
⚛ (Calling Lifecycle Methods: 1 Total)
|
||||
⛔ Cascading.componentDidMount Warning: Scheduled a cascading update
|
||||
|
||||
⚛ (React Tree Reconciliation)
|
||||
⚛ (React Tree Reconciliation: Completed Root)
|
||||
⚛ Cascading [update]
|
||||
|
||||
⚛ (Committing Changes)
|
||||
⚛ (Committing Snapshot Effects: 0 Total)
|
||||
⚛ (Committing Host Effects: 2 Total)
|
||||
⚛ (Calling Lifecycle Methods: 2 Total)
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`ReactDebugFiberPerf warns on cascading renders from top-level render 1`] = `
|
||||
"⚛ (Waiting for async callback...)
|
||||
"⚛ (Waiting for async callback... will force flush in 5230 ms)
|
||||
|
||||
// Rendering the first root
|
||||
⚛ (React Tree Reconciliation)
|
||||
⚛ (React Tree Reconciliation: Completed Root)
|
||||
⚛ Cascading [mount]
|
||||
|
||||
⛔ (Committing Changes) Warning: Lifecycle hook scheduled a cascading update
|
||||
⚛ (Committing Snapshot Effects: 0 Total)
|
||||
⚛ (Committing Host Effects: 1 Total)
|
||||
⚛ (Calling Lifecycle Methods: 1 Total)
|
||||
⛔ Cascading.componentDidMount Warning: Scheduled a cascading update
|
||||
|
||||
// Scheduling another root from componentDidMount
|
||||
⚛ (React Tree Reconciliation)
|
||||
⚛ (React Tree Reconciliation: Completed Root)
|
||||
⚛ Child [mount]
|
||||
|
||||
⚛ (Committing Changes)
|
||||
⚛ (Committing Snapshot Effects: 0 Total)
|
||||
⚛ (Committing Host Effects: 1 Total)
|
||||
⚛ (Calling Lifecycle Methods: 0 Total)
|
||||
"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "react-test-renderer",
|
||||
"version": "16.3.0-alpha.2",
|
||||
"version": "16.3.1",
|
||||
"description": "React package for snapshot testing.",
|
||||
"main": "index.js",
|
||||
"repository": "facebook/react",
|
||||
@@ -17,10 +17,11 @@
|
||||
"dependencies": {
|
||||
"fbjs": "^0.8.16",
|
||||
"object-assign": "^4.1.1",
|
||||
"prop-types": "^15.6.0"
|
||||
"prop-types": "^15.6.0",
|
||||
"react-is": "^16.3.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.0.0 || 16.3.0-alpha.2"
|
||||
"react": "^16.0.0"
|
||||
},
|
||||
"files": [
|
||||
"LICENSE",
|
||||
|
||||
@@ -7,33 +7,13 @@
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import {warnAboutDeprecatedLifecycles} from 'shared/ReactFeatureFlags';
|
||||
import {isForwardRef} from 'react-is';
|
||||
import describeComponentFrame from 'shared/describeComponentFrame';
|
||||
import getComponentName from 'shared/getComponentName';
|
||||
import emptyObject from 'fbjs/lib/emptyObject';
|
||||
import invariant from 'fbjs/lib/invariant';
|
||||
import lowPriorityWarning from 'shared/lowPriorityWarning';
|
||||
import shallowEqual from 'fbjs/lib/shallowEqual';
|
||||
import checkPropTypes from 'prop-types/checkPropTypes';
|
||||
import warning from 'fbjs/lib/warning';
|
||||
|
||||
let didWarnAboutLegacyWillMount;
|
||||
let didWarnAboutLegacyWillReceiveProps;
|
||||
let didWarnAboutLegacyWillUpdate;
|
||||
let didWarnAboutUndefinedDerivedState;
|
||||
let didWarnAboutUninitializedState;
|
||||
let didWarnAboutWillReceivePropsAndDerivedState;
|
||||
|
||||
if (__DEV__) {
|
||||
if (warnAboutDeprecatedLifecycles) {
|
||||
didWarnAboutLegacyWillMount = {};
|
||||
didWarnAboutLegacyWillReceiveProps = {};
|
||||
didWarnAboutLegacyWillUpdate = {};
|
||||
}
|
||||
didWarnAboutUndefinedDerivedState = {};
|
||||
didWarnAboutUninitializedState = {};
|
||||
didWarnAboutWillReceivePropsAndDerivedState = {};
|
||||
}
|
||||
|
||||
class ReactShallowRenderer {
|
||||
static createRenderer = function() {
|
||||
@@ -77,7 +57,7 @@ class ReactShallowRenderer {
|
||||
element.type,
|
||||
);
|
||||
invariant(
|
||||
typeof element.type === 'function',
|
||||
isForwardRef(element) || typeof element.type === 'function',
|
||||
'ReactShallowRenderer render(): Shallow rendering works only with custom ' +
|
||||
'components, but the provided element type was `%s`.',
|
||||
Array.isArray(element.type)
|
||||
@@ -96,35 +76,15 @@ class ReactShallowRenderer {
|
||||
if (this._instance) {
|
||||
this._updateClassComponent(element, this._context);
|
||||
} else {
|
||||
if (shouldConstruct(element.type)) {
|
||||
if (isForwardRef(element)) {
|
||||
this._rendered = element.type.render(element.props, element.ref);
|
||||
} else if (shouldConstruct(element.type)) {
|
||||
this._instance = new element.type(
|
||||
element.props,
|
||||
this._context,
|
||||
this._updater,
|
||||
);
|
||||
|
||||
if (__DEV__) {
|
||||
if (typeof element.type.getDerivedStateFromProps === 'function') {
|
||||
if (
|
||||
this._instance.state === null ||
|
||||
this._instance.state === undefined
|
||||
) {
|
||||
const componentName =
|
||||
getName(element.type, this._instance) || 'Unknown';
|
||||
if (!didWarnAboutUninitializedState[componentName]) {
|
||||
warning(
|
||||
false,
|
||||
'%s: Did not properly initialize state during construction. ' +
|
||||
'Expected state to be an object, but it was %s.',
|
||||
componentName,
|
||||
this._instance.state === null ? 'null' : 'undefined',
|
||||
);
|
||||
didWarnAboutUninitializedState[componentName] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this._updateStateFromStaticLifecycle(element.props);
|
||||
|
||||
if (element.type.hasOwnProperty('contextTypes')) {
|
||||
@@ -179,45 +139,18 @@ class ReactShallowRenderer {
|
||||
) {
|
||||
const beforeState = this._newState;
|
||||
|
||||
if (typeof this._instance.componentWillMount === 'function') {
|
||||
if (__DEV__) {
|
||||
// Don't warn about react-lifecycles-compat polyfilled components
|
||||
if (
|
||||
warnAboutDeprecatedLifecycles &&
|
||||
this._instance.componentWillMount.__suppressDeprecationWarning !==
|
||||
true
|
||||
) {
|
||||
const componentName = getName(element.type, this._instance);
|
||||
if (!didWarnAboutLegacyWillMount[componentName]) {
|
||||
lowPriorityWarning(
|
||||
false,
|
||||
'%s: componentWillMount() is deprecated and will be ' +
|
||||
'removed in the next major version. Read about the motivations ' +
|
||||
'behind this change: ' +
|
||||
'https://fb.me/react-async-component-lifecycle-hooks' +
|
||||
'\n\n' +
|
||||
'As a temporary workaround, you can rename to ' +
|
||||
'UNSAFE_componentWillMount instead.',
|
||||
componentName,
|
||||
);
|
||||
didWarnAboutLegacyWillMount[componentName] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// In order to support react-lifecycles-compat polyfilled components,
|
||||
// Unsafe lifecycles should not be invoked for any component with the new gDSFP.
|
||||
if (typeof element.type.getDerivedStateFromProps !== 'function') {
|
||||
// In order to support react-lifecycles-compat polyfilled components,
|
||||
// Unsafe lifecycles should not be invoked for components using the new APIs.
|
||||
if (
|
||||
typeof element.type.getDerivedStateFromProps !== 'function' &&
|
||||
typeof this._instance.getSnapshotBeforeUpdate !== 'function'
|
||||
) {
|
||||
if (typeof this._instance.componentWillMount === 'function') {
|
||||
this._instance.componentWillMount();
|
||||
}
|
||||
}
|
||||
if (
|
||||
typeof this._instance.UNSAFE_componentWillMount === 'function' &&
|
||||
typeof element.type.getDerivedStateFromProps !== 'function'
|
||||
) {
|
||||
// In order to support react-lifecycles-compat polyfilled components,
|
||||
// Unsafe lifecycles should not be invoked for any component with the new gDSFP.
|
||||
this._instance.UNSAFE_componentWillMount();
|
||||
if (typeof this._instance.UNSAFE_componentWillMount === 'function') {
|
||||
this._instance.UNSAFE_componentWillMount();
|
||||
}
|
||||
}
|
||||
|
||||
// setState may have been called during cWM
|
||||
@@ -238,40 +171,20 @@ class ReactShallowRenderer {
|
||||
const oldProps = this._instance.props;
|
||||
|
||||
if (oldProps !== props) {
|
||||
if (typeof this._instance.componentWillReceiveProps === 'function') {
|
||||
if (__DEV__) {
|
||||
if (warnAboutDeprecatedLifecycles) {
|
||||
const componentName = getName(element.type, this._instance);
|
||||
if (!didWarnAboutLegacyWillReceiveProps[componentName]) {
|
||||
lowPriorityWarning(
|
||||
false,
|
||||
'%s: componentWillReceiveProps() is deprecated and ' +
|
||||
'will be removed in the next major version. Use ' +
|
||||
'static getDerivedStateFromProps() instead. Read about the ' +
|
||||
'motivations behind this change: ' +
|
||||
'https://fb.me/react-async-component-lifecycle-hooks' +
|
||||
'\n\n' +
|
||||
'As a temporary workaround, you can rename to ' +
|
||||
'UNSAFE_componentWillReceiveProps instead.',
|
||||
componentName,
|
||||
);
|
||||
didWarnAboutLegacyWillReceiveProps[componentName] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
// In order to support react-lifecycles-compat polyfilled components,
|
||||
// Unsafe lifecycles should not be invoked for any component with the new gDSFP.
|
||||
if (typeof element.type.getDerivedStateFromProps !== 'function') {
|
||||
// In order to support react-lifecycles-compat polyfilled components,
|
||||
// Unsafe lifecycles should not be invoked for components using the new APIs.
|
||||
if (
|
||||
typeof element.type.getDerivedStateFromProps !== 'function' &&
|
||||
typeof this._instance.getSnapshotBeforeUpdate !== 'function'
|
||||
) {
|
||||
if (typeof this._instance.componentWillReceiveProps === 'function') {
|
||||
this._instance.componentWillReceiveProps(props, context);
|
||||
}
|
||||
}
|
||||
if (
|
||||
typeof this._instance.UNSAFE_componentWillReceiveProps === 'function' &&
|
||||
typeof element.type.getDerivedStateFromProps !== 'function'
|
||||
) {
|
||||
// In order to support react-lifecycles-compat polyfilled components,
|
||||
// Unsafe lifecycles should not be invoked for any component with the new gDSFP.
|
||||
this._instance.UNSAFE_componentWillReceiveProps(props, context);
|
||||
if (
|
||||
typeof this._instance.UNSAFE_componentWillReceiveProps === 'function'
|
||||
) {
|
||||
this._instance.UNSAFE_componentWillReceiveProps(props, context);
|
||||
}
|
||||
}
|
||||
|
||||
this._updateStateFromStaticLifecycle(props);
|
||||
@@ -296,40 +209,18 @@ class ReactShallowRenderer {
|
||||
}
|
||||
|
||||
if (shouldUpdate) {
|
||||
if (typeof this._instance.componentWillUpdate === 'function') {
|
||||
if (__DEV__) {
|
||||
if (warnAboutDeprecatedLifecycles) {
|
||||
const componentName = getName(element.type, this._instance);
|
||||
if (!didWarnAboutLegacyWillUpdate[componentName]) {
|
||||
lowPriorityWarning(
|
||||
false,
|
||||
'%s: componentWillUpdate() is deprecated and will be ' +
|
||||
'removed in the next major version. Read about the motivations ' +
|
||||
'behind this change: ' +
|
||||
'https://fb.me/react-async-component-lifecycle-hooks' +
|
||||
'\n\n' +
|
||||
'As a temporary workaround, you can rename to ' +
|
||||
'UNSAFE_componentWillUpdate instead.',
|
||||
componentName,
|
||||
);
|
||||
didWarnAboutLegacyWillUpdate[componentName] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// In order to support react-lifecycles-compat polyfilled components,
|
||||
// Unsafe lifecycles should not be invoked for any component with the new gDSFP.
|
||||
if (typeof type.getDerivedStateFromProps !== 'function') {
|
||||
// In order to support react-lifecycles-compat polyfilled components,
|
||||
// Unsafe lifecycles should not be invoked for components using the new APIs.
|
||||
if (
|
||||
typeof element.type.getDerivedStateFromProps !== 'function' &&
|
||||
typeof this._instance.getSnapshotBeforeUpdate !== 'function'
|
||||
) {
|
||||
if (typeof this._instance.componentWillUpdate === 'function') {
|
||||
this._instance.componentWillUpdate(props, state, context);
|
||||
}
|
||||
}
|
||||
if (
|
||||
typeof this._instance.UNSAFE_componentWillUpdate === 'function' &&
|
||||
typeof type.getDerivedStateFromProps !== 'function'
|
||||
) {
|
||||
// In order to support react-lifecycles-compat polyfilled components,
|
||||
// Unsafe lifecycles should not be invoked for any component with the new gDSFP.
|
||||
this._instance.UNSAFE_componentWillUpdate(props, state, context);
|
||||
if (typeof this._instance.UNSAFE_componentWillUpdate === 'function') {
|
||||
this._instance.UNSAFE_componentWillUpdate(props, state, context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -348,49 +239,12 @@ class ReactShallowRenderer {
|
||||
const {type} = this._element;
|
||||
|
||||
if (typeof type.getDerivedStateFromProps === 'function') {
|
||||
if (__DEV__) {
|
||||
// Don't warn about react-lifecycles-compat polyfilled components
|
||||
if (
|
||||
(typeof this._instance.componentWillReceiveProps === 'function' &&
|
||||
this._instance.componentWillReceiveProps
|
||||
.__suppressDeprecationWarning !== true) ||
|
||||
typeof this._instance.UNSAFE_componentWillReceiveProps === 'function'
|
||||
) {
|
||||
const componentName = getName(type, this._instance);
|
||||
if (!didWarnAboutWillReceivePropsAndDerivedState[componentName]) {
|
||||
warning(
|
||||
false,
|
||||
'%s: Defines both componentWillReceiveProps() and static ' +
|
||||
'getDerivedStateFromProps() methods. We recommend using ' +
|
||||
'only getDerivedStateFromProps().',
|
||||
componentName,
|
||||
);
|
||||
didWarnAboutWillReceivePropsAndDerivedState[componentName] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const partialState = type.getDerivedStateFromProps.call(
|
||||
null,
|
||||
props,
|
||||
this._instance.state,
|
||||
);
|
||||
|
||||
if (__DEV__) {
|
||||
if (partialState === undefined) {
|
||||
const componentName = getName(type, this._instance);
|
||||
if (!didWarnAboutUndefinedDerivedState[componentName]) {
|
||||
warning(
|
||||
false,
|
||||
'%s.getDerivedStateFromProps(): A valid state object (or null) must be returned. ' +
|
||||
'You have returned undefined.',
|
||||
componentName,
|
||||
);
|
||||
didWarnAboutUndefinedDerivedState[componentName] = componentName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (partialState != null) {
|
||||
const oldState = this._newState || this._instance.state;
|
||||
const newState = Object.assign({}, oldState, partialState);
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
import type {Fiber} from 'react-reconciler/src/ReactFiber';
|
||||
import type {FiberRoot} from 'react-reconciler/src/ReactFiberRoot';
|
||||
import type {Deadline} from 'react-reconciler/src/ReactFiberReconciler';
|
||||
|
||||
import ReactFiberReconciler from 'react-reconciler';
|
||||
import {batchedUpdates} from 'events/ReactGenericBatching';
|
||||
@@ -25,11 +26,13 @@ import {
|
||||
ContextConsumer,
|
||||
ContextProvider,
|
||||
Mode,
|
||||
ForwardRef,
|
||||
} from 'shared/ReactTypeOfWork';
|
||||
import invariant from 'fbjs/lib/invariant';
|
||||
|
||||
type TestRendererOptions = {
|
||||
createNodeMock: (element: React$Element<any>) => any,
|
||||
unstable_isAsync: boolean,
|
||||
};
|
||||
|
||||
type ReactTestRendererJSON = {|
|
||||
@@ -115,6 +118,11 @@ function removeChild(
|
||||
parentInstance.children.splice(index, 1);
|
||||
}
|
||||
|
||||
// Current virtual time
|
||||
let currentTime: number = 0;
|
||||
let scheduledCallback: ((deadline: Deadline) => mixed) | null = null;
|
||||
let yieldedValues: Array<mixed> | null = null;
|
||||
|
||||
const TestRenderer = ReactFiberReconciler({
|
||||
getRootHostContext() {
|
||||
return emptyObject;
|
||||
@@ -199,19 +207,22 @@ const TestRenderer = ReactFiberReconciler({
|
||||
};
|
||||
},
|
||||
|
||||
scheduleDeferredCallback(fn: Function): number {
|
||||
return setTimeout(fn, 0, {timeRemaining: Infinity});
|
||||
scheduleDeferredCallback(
|
||||
callback: (deadline: Deadline) => mixed,
|
||||
options?: {timeout: number},
|
||||
): number {
|
||||
scheduledCallback = callback;
|
||||
return 0;
|
||||
},
|
||||
|
||||
cancelDeferredCallback(timeoutID: number): void {
|
||||
clearTimeout(timeoutID);
|
||||
scheduledCallback = null;
|
||||
},
|
||||
|
||||
getPublicInstance,
|
||||
|
||||
now(): number {
|
||||
// Test renderer does not use expiration
|
||||
return 0;
|
||||
return currentTime;
|
||||
},
|
||||
|
||||
mutation: {
|
||||
@@ -372,6 +383,7 @@ function toTree(node: ?Fiber) {
|
||||
case ContextProvider:
|
||||
case ContextConsumer:
|
||||
case Mode:
|
||||
case ForwardRef:
|
||||
return childrenToTree(node.child);
|
||||
default:
|
||||
invariant(
|
||||
@@ -472,6 +484,7 @@ class ReactTestInstance {
|
||||
case ContextProvider:
|
||||
case ContextConsumer:
|
||||
case Mode:
|
||||
case ForwardRef:
|
||||
descend = true;
|
||||
break;
|
||||
default:
|
||||
@@ -600,8 +613,14 @@ function propsMatch(props: Object, filter: Object): boolean {
|
||||
const ReactTestRendererFiber = {
|
||||
create(element: React$Element<any>, options: TestRendererOptions) {
|
||||
let createNodeMock = defaultTestOptions.createNodeMock;
|
||||
if (options && typeof options.createNodeMock === 'function') {
|
||||
createNodeMock = options.createNodeMock;
|
||||
let isAsync = false;
|
||||
if (typeof options === 'object' && options !== null) {
|
||||
if (typeof options.createNodeMock === 'function') {
|
||||
createNodeMock = options.createNodeMock;
|
||||
}
|
||||
if (options.unstable_isAsync === true) {
|
||||
isAsync = true;
|
||||
}
|
||||
}
|
||||
let container = {
|
||||
children: [],
|
||||
@@ -610,7 +629,7 @@ const ReactTestRendererFiber = {
|
||||
};
|
||||
let root: FiberRoot | null = TestRenderer.createContainer(
|
||||
container,
|
||||
false,
|
||||
isAsync,
|
||||
false,
|
||||
);
|
||||
invariant(root != null, 'something went wrong');
|
||||
@@ -651,6 +670,66 @@ const ReactTestRendererFiber = {
|
||||
container = null;
|
||||
root = null;
|
||||
},
|
||||
unstable_flushAll(): Array<mixed> {
|
||||
yieldedValues = null;
|
||||
while (scheduledCallback !== null) {
|
||||
const cb = scheduledCallback;
|
||||
scheduledCallback = null;
|
||||
cb({
|
||||
timeRemaining() {
|
||||
// Keep rendering until there's no more work
|
||||
return 999;
|
||||
},
|
||||
// React's scheduler has its own way of keeping track of expired
|
||||
// work and doesn't read this, so don't bother setting it to the
|
||||
// correct value.
|
||||
didTimeout: false,
|
||||
});
|
||||
}
|
||||
if (yieldedValues === null) {
|
||||
// Always return an array.
|
||||
return [];
|
||||
}
|
||||
return yieldedValues;
|
||||
},
|
||||
unstable_flushThrough(expectedValues: Array<mixed>): Array<mixed> {
|
||||
let didStop = false;
|
||||
yieldedValues = null;
|
||||
while (scheduledCallback !== null && !didStop) {
|
||||
const cb = scheduledCallback;
|
||||
scheduledCallback = null;
|
||||
cb({
|
||||
timeRemaining() {
|
||||
if (
|
||||
yieldedValues !== null &&
|
||||
yieldedValues.length >= expectedValues.length
|
||||
) {
|
||||
// We at least as many values as expected. Stop rendering.
|
||||
didStop = true;
|
||||
return 0;
|
||||
}
|
||||
// Keep rendering.
|
||||
return 999;
|
||||
},
|
||||
// React's scheduler has its own way of keeping track of expired
|
||||
// work and doesn't read this, so don't bother setting it to the
|
||||
// correct value.
|
||||
didTimeout: false,
|
||||
});
|
||||
}
|
||||
if (yieldedValues === null) {
|
||||
// Always return an array.
|
||||
return [];
|
||||
}
|
||||
return yieldedValues;
|
||||
},
|
||||
unstable_yield(value: mixed): void {
|
||||
if (yieldedValues === null) {
|
||||
yieldedValues = [value];
|
||||
} else {
|
||||
yieldedValues.push(value);
|
||||
}
|
||||
},
|
||||
getInstance() {
|
||||
if (root == null || root.current == null) {
|
||||
return null;
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2013-present, Facebook, Inc.
|
||||
*
|
||||
* 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
|
||||
* @jest-environment node
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
let createRenderer;
|
||||
let React;
|
||||
let ReactFeatureFlags;
|
||||
|
||||
describe('ReactShallowRenderer', () => {
|
||||
beforeEach(() => {
|
||||
ReactFeatureFlags = require('shared/ReactFeatureFlags');
|
||||
ReactFeatureFlags.warnAboutDeprecatedLifecycles = true;
|
||||
|
||||
createRenderer = require('react-test-renderer/shallow').createRenderer;
|
||||
React = require('react');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.resetModules();
|
||||
});
|
||||
|
||||
// TODO (RFC #6) Merge this back into ReactShallowRenderer-test once
|
||||
// the 'warnAboutDeprecatedLifecycles' feature flag has been removed.
|
||||
it('should warn if deprecated lifecycles exist', () => {
|
||||
class ComponentWithWarnings extends React.Component {
|
||||
componentWillReceiveProps() {}
|
||||
componentWillMount() {}
|
||||
componentWillUpdate() {}
|
||||
render() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
const shallowRenderer = createRenderer();
|
||||
expect(() =>
|
||||
shallowRenderer.render(<ComponentWithWarnings />),
|
||||
).toLowPriorityWarnDev(
|
||||
'Warning: ComponentWithWarnings: componentWillMount() is deprecated and will ' +
|
||||
'be removed in the next major version.',
|
||||
);
|
||||
expect(() =>
|
||||
shallowRenderer.render(<ComponentWithWarnings />),
|
||||
).toLowPriorityWarnDev([
|
||||
'Warning: ComponentWithWarnings: componentWillReceiveProps() is deprecated ' +
|
||||
'and will be removed in the next major version.',
|
||||
'Warning: ComponentWithWarnings: componentWillUpdate() is deprecated and will ' +
|
||||
'be removed in the next major version.',
|
||||
]);
|
||||
|
||||
// Verify no duplicate warnings
|
||||
shallowRenderer.render(<ComponentWithWarnings />);
|
||||
});
|
||||
|
||||
describe('react-lifecycles-compat', () => {
|
||||
const polyfill = require('react-lifecycles-compat');
|
||||
|
||||
it('should not warn about deprecated cWM/cWRP for polyfilled components', () => {
|
||||
class PolyfilledComponent extends React.Component {
|
||||
state = {};
|
||||
static getDerivedStateFromProps() {
|
||||
return null;
|
||||
}
|
||||
render() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
polyfill(PolyfilledComponent);
|
||||
|
||||
const shallowRenderer = createRenderer();
|
||||
shallowRenderer.render(<PolyfilledComponent />);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -125,9 +125,49 @@ describe('ReactShallowRenderer', () => {
|
||||
}
|
||||
|
||||
const shallowRenderer = createRenderer();
|
||||
expect(() => shallowRenderer.render(<Component foo={2} />)).toWarnDev(
|
||||
'Defines both componentWillReceiveProps() and static getDerivedStateFromProps()',
|
||||
);
|
||||
shallowRenderer.render(<Component />);
|
||||
});
|
||||
|
||||
it('should not invoke deprecated lifecycles (cWM/cWRP/cWU) if new getSnapshotBeforeUpdate is present', () => {
|
||||
class Component extends React.Component {
|
||||
getSnapshotBeforeUpdate() {
|
||||
return null;
|
||||
}
|
||||
componentWillMount() {
|
||||
throw Error('unexpected');
|
||||
}
|
||||
componentWillReceiveProps() {
|
||||
throw Error('unexpected');
|
||||
}
|
||||
componentWillUpdate() {
|
||||
throw Error('unexpected');
|
||||
}
|
||||
render() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
const shallowRenderer = createRenderer();
|
||||
shallowRenderer.render(<Component value={1} />);
|
||||
shallowRenderer.render(<Component value={2} />);
|
||||
});
|
||||
|
||||
it('should not call getSnapshotBeforeUpdate or componentDidUpdate when updating since refs wont exist', () => {
|
||||
class Component extends React.Component {
|
||||
getSnapshotBeforeUpdate() {
|
||||
throw Error('unexpected');
|
||||
}
|
||||
componentDidUpdate() {
|
||||
throw Error('unexpected');
|
||||
}
|
||||
render() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
const shallowRenderer = createRenderer();
|
||||
shallowRenderer.render(<Component value={1} />);
|
||||
shallowRenderer.render(<Component value={2} />);
|
||||
});
|
||||
|
||||
it('should only render 1 level deep', () => {
|
||||
@@ -168,6 +208,28 @@ describe('ReactShallowRenderer', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it('should handle ForwardRef', () => {
|
||||
const testRef = React.createRef();
|
||||
const SomeComponent = React.forwardRef((props, ref) => {
|
||||
expect(ref).toEqual(testRef);
|
||||
return (
|
||||
<div>
|
||||
<span className="child1" />
|
||||
<span className="child2" />
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
const shallowRenderer = createRenderer();
|
||||
const result = shallowRenderer.render(<SomeComponent ref={testRef} />);
|
||||
|
||||
expect(result.type).toBe('div');
|
||||
expect(result.props.children).toEqual([
|
||||
<span className="child1" />,
|
||||
<span className="child2" />,
|
||||
]);
|
||||
});
|
||||
|
||||
it('should enable shouldComponentUpdate to prevent a re-render', () => {
|
||||
let renderCounter = 0;
|
||||
class SimpleComponent extends React.Component {
|
||||
@@ -1202,68 +1264,6 @@ describe('ReactShallowRenderer', () => {
|
||||
expect(shallowRenderer.getMountedInstance().state).toBeNull();
|
||||
});
|
||||
|
||||
it('should warn if both componentWillReceiveProps and static getDerivedStateFromProps exist', () => {
|
||||
class ComponentWithWarnings extends React.Component {
|
||||
state = {};
|
||||
static getDerivedStateFromProps(props, prevState) {
|
||||
return null;
|
||||
}
|
||||
UNSAFE_componentWillReceiveProps(nextProps) {}
|
||||
render() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
const shallowRenderer = createRenderer();
|
||||
expect(() => shallowRenderer.render(<ComponentWithWarnings />)).toWarnDev(
|
||||
'ComponentWithWarnings: Defines both componentWillReceiveProps() and static ' +
|
||||
'getDerivedStateFromProps() methods. We recommend using ' +
|
||||
'only getDerivedStateFromProps().',
|
||||
);
|
||||
|
||||
// Should not log duplicate warning
|
||||
shallowRenderer.render(<ComponentWithWarnings />);
|
||||
});
|
||||
|
||||
it('should warn if getDerivedStateFromProps returns undefined', () => {
|
||||
class Component extends React.Component {
|
||||
state = {};
|
||||
static getDerivedStateFromProps() {}
|
||||
render() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
const shallowRenderer = createRenderer();
|
||||
expect(() => shallowRenderer.render(<Component />)).toWarnDev(
|
||||
'Component.getDerivedStateFromProps(): A valid state object (or null) must ' +
|
||||
'be returned. You have returned undefined.',
|
||||
);
|
||||
|
||||
// De-duped
|
||||
shallowRenderer.render(<Component />);
|
||||
});
|
||||
|
||||
it('should warn if state not initialized before getDerivedStateFromProps', () => {
|
||||
class Component extends React.Component {
|
||||
static getDerivedStateFromProps() {
|
||||
return null;
|
||||
}
|
||||
render() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
const shallowRenderer = createRenderer();
|
||||
expect(() => shallowRenderer.render(<Component />)).toWarnDev(
|
||||
'Component: Did not properly initialize state during construction. ' +
|
||||
'Expected state to be an object, but it was undefined.',
|
||||
);
|
||||
|
||||
// De-duped
|
||||
shallowRenderer.render(<Component />);
|
||||
});
|
||||
|
||||
it('should invoke both deprecated and new lifecycles if both are present', () => {
|
||||
const log = [];
|
||||
|
||||
|
||||
@@ -404,6 +404,7 @@ describe('ReactTestRenderer', () => {
|
||||
log.push('Angry render');
|
||||
throw new Error('Please, do not render me.');
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
log.push('Angry componentDidMount');
|
||||
}
|
||||
@@ -957,4 +958,46 @@ describe('ReactTestRenderer', () => {
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('supports forwardRef', () => {
|
||||
const InnerRefed = React.forwardRef((props, ref) => (
|
||||
<div>
|
||||
<span ref={ref} />
|
||||
</div>
|
||||
));
|
||||
|
||||
class App extends React.Component {
|
||||
render() {
|
||||
return <InnerRefed ref={r => (this.ref = r)} />;
|
||||
}
|
||||
}
|
||||
|
||||
const renderer = ReactTestRenderer.create(<App />);
|
||||
const tree = renderer.toTree();
|
||||
cleanNodeOrArray(tree);
|
||||
|
||||
expect(prettyFormat(tree)).toEqual(
|
||||
prettyFormat({
|
||||
instance: null,
|
||||
nodeType: 'component',
|
||||
props: {},
|
||||
rendered: {
|
||||
instance: null,
|
||||
nodeType: 'host',
|
||||
props: {},
|
||||
rendered: [
|
||||
{
|
||||
instance: null,
|
||||
nodeType: 'host',
|
||||
props: {},
|
||||
rendered: [],
|
||||
type: 'span',
|
||||
},
|
||||
],
|
||||
type: 'div',
|
||||
},
|
||||
type: App,
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
97
packages/react-test-renderer/src/__tests__/ReactTestRendererAsync-test.js
vendored
Normal file
97
packages/react-test-renderer/src/__tests__/ReactTestRendererAsync-test.js
vendored
Normal file
@@ -0,0 +1,97 @@
|
||||
/**
|
||||
* Copyright (c) 2013-present, Facebook, Inc.
|
||||
*
|
||||
* 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
|
||||
* @jest-environment node
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const React = require('react');
|
||||
const ReactTestRenderer = require('react-test-renderer');
|
||||
|
||||
describe('ReactTestRendererAsync', () => {
|
||||
it('flushAll flushes all work', () => {
|
||||
function Foo(props) {
|
||||
return props.children;
|
||||
}
|
||||
const renderer = ReactTestRenderer.create(<Foo>Hi</Foo>, {
|
||||
unstable_isAsync: true,
|
||||
});
|
||||
|
||||
// Before flushing, nothing has mounted.
|
||||
expect(renderer.toJSON()).toEqual(null);
|
||||
|
||||
// Flush initial mount.
|
||||
renderer.unstable_flushAll();
|
||||
expect(renderer.toJSON()).toEqual('Hi');
|
||||
|
||||
// Update
|
||||
renderer.update(<Foo>Bye</Foo>);
|
||||
// Not yet updated.
|
||||
expect(renderer.toJSON()).toEqual('Hi');
|
||||
// Flush update.
|
||||
renderer.unstable_flushAll();
|
||||
expect(renderer.toJSON()).toEqual('Bye');
|
||||
});
|
||||
|
||||
it('flushAll returns array of yielded values', () => {
|
||||
function Child(props) {
|
||||
renderer.unstable_yield(props.children);
|
||||
return props.children;
|
||||
}
|
||||
function Parent(props) {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Child>{'A:' + props.step}</Child>
|
||||
<Child>{'B:' + props.step}</Child>
|
||||
<Child>{'C:' + props.step}</Child>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
const renderer = ReactTestRenderer.create(<Parent step={1} />, {
|
||||
unstable_isAsync: true,
|
||||
});
|
||||
|
||||
expect(renderer.unstable_flushAll()).toEqual(['A:1', 'B:1', 'C:1']);
|
||||
expect(renderer.toJSON()).toEqual(['A:1', 'B:1', 'C:1']);
|
||||
|
||||
renderer.update(<Parent step={2} />);
|
||||
expect(renderer.unstable_flushAll()).toEqual(['A:2', 'B:2', 'C:2']);
|
||||
expect(renderer.toJSON()).toEqual(['A:2', 'B:2', 'C:2']);
|
||||
});
|
||||
|
||||
it('flushThrough flushes until the expected values is yielded', () => {
|
||||
function Child(props) {
|
||||
renderer.unstable_yield(props.children);
|
||||
return props.children;
|
||||
}
|
||||
function Parent(props) {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Child>{'A:' + props.step}</Child>
|
||||
<Child>{'B:' + props.step}</Child>
|
||||
<Child>{'C:' + props.step}</Child>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
const renderer = ReactTestRenderer.create(<Parent step={1} />, {
|
||||
unstable_isAsync: true,
|
||||
});
|
||||
|
||||
// Flush the first two siblings
|
||||
expect(renderer.unstable_flushThrough(['A:1', 'B:1'])).toEqual([
|
||||
'A:1',
|
||||
'B:1',
|
||||
]);
|
||||
// Did not commit yet.
|
||||
expect(renderer.toJSON()).toEqual(null);
|
||||
|
||||
// Flush the remaining work
|
||||
expect(renderer.unstable_flushAll()).toEqual(['C:1']);
|
||||
expect(renderer.toJSON()).toEqual(['A:1', 'B:1', 'C:1']);
|
||||
});
|
||||
});
|
||||
@@ -4,7 +4,7 @@
|
||||
"keywords": [
|
||||
"react"
|
||||
],
|
||||
"version": "16.3.0-alpha.2",
|
||||
"version": "16.3.1",
|
||||
"homepage": "https://reactjs.org/",
|
||||
"bugs": "https://github.com/facebook/react/issues",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -44,7 +44,7 @@ export function createContext<T>(
|
||||
|
||||
context.Provider = {
|
||||
$$typeof: REACT_PROVIDER_TYPE,
|
||||
context,
|
||||
_context: context,
|
||||
};
|
||||
context.Consumer = context;
|
||||
|
||||
|
||||
@@ -14,16 +14,9 @@
|
||||
|
||||
import lowPriorityWarning from 'shared/lowPriorityWarning';
|
||||
import describeComponentFrame from 'shared/describeComponentFrame';
|
||||
import isValidElementType from 'shared/isValidElementType';
|
||||
import getComponentName from 'shared/getComponentName';
|
||||
import {
|
||||
getIteratorFn,
|
||||
REACT_FRAGMENT_TYPE,
|
||||
REACT_STRICT_MODE_TYPE,
|
||||
REACT_ASYNC_MODE_TYPE,
|
||||
REACT_PROVIDER_TYPE,
|
||||
REACT_CONTEXT_TYPE,
|
||||
REACT_FORWARD_REF_TYPE,
|
||||
} from 'shared/ReactSymbols';
|
||||
import {getIteratorFn, REACT_FRAGMENT_TYPE} from 'shared/ReactSymbols';
|
||||
import checkPropTypes from 'prop-types/checkPropTypes';
|
||||
import warning from 'fbjs/lib/warning';
|
||||
|
||||
@@ -37,8 +30,6 @@ let propTypesMisspellWarningShown;
|
||||
let getDisplayName = () => {};
|
||||
let getStackAddendum = () => {};
|
||||
|
||||
let VALID_FRAGMENT_PROPS;
|
||||
|
||||
if (__DEV__) {
|
||||
currentlyValidatingElement = null;
|
||||
|
||||
@@ -72,8 +63,6 @@ if (__DEV__) {
|
||||
stack += ReactDebugCurrentFrame.getStackAddendum() || '';
|
||||
return stack;
|
||||
};
|
||||
|
||||
VALID_FRAGMENT_PROPS = new Map([['children', true], ['key', true]]);
|
||||
}
|
||||
|
||||
function getDeclarationErrorAddendum() {
|
||||
@@ -264,7 +253,7 @@ function validateFragmentProps(fragment) {
|
||||
const keys = Object.keys(fragment.props);
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const key = keys[i];
|
||||
if (!VALID_FRAGMENT_PROPS.has(key)) {
|
||||
if (key !== 'children' && key !== 'key') {
|
||||
warning(
|
||||
false,
|
||||
'Invalid prop `%s` supplied to `React.Fragment`. ' +
|
||||
@@ -288,18 +277,7 @@ function validateFragmentProps(fragment) {
|
||||
}
|
||||
|
||||
export function createElementWithValidation(type, props, children) {
|
||||
const validType =
|
||||
typeof type === 'string' ||
|
||||
typeof type === 'function' ||
|
||||
// Note: its typeof might be other than 'symbol' or 'number' if it's a polyfill.
|
||||
type === REACT_FRAGMENT_TYPE ||
|
||||
type === REACT_ASYNC_MODE_TYPE ||
|
||||
type === REACT_STRICT_MODE_TYPE ||
|
||||
(typeof type === 'object' &&
|
||||
type !== null &&
|
||||
(type.$$typeof === REACT_PROVIDER_TYPE ||
|
||||
type.$$typeof === REACT_CONTEXT_TYPE ||
|
||||
type.$$typeof === REACT_FORWARD_REF_TYPE));
|
||||
const validType = isValidElementType(type);
|
||||
|
||||
// We warn in this case but don't throw. We expect the element creation to
|
||||
// succeed and there will likely be errors in render.
|
||||
|
||||
@@ -21,10 +21,10 @@ function warnNoop(publicInstance, callerName) {
|
||||
}
|
||||
warning(
|
||||
false,
|
||||
'%s(...): Can only update a mounted or mounting component. ' +
|
||||
'This usually means you called %s() on an unmounted component. ' +
|
||||
'This is a no-op.\n\nPlease check the code for the %s component.',
|
||||
callerName,
|
||||
"Can't call %s on a component that is not yet mounted. " +
|
||||
'This is a no-op, but it might indicate a bug in your application. ' +
|
||||
'Instead, assign to `this.state` directly or define a `state = {};` ' +
|
||||
'class property with the desired state in the %s component.',
|
||||
callerName,
|
||||
componentName,
|
||||
);
|
||||
|
||||
@@ -118,6 +118,39 @@ describe 'ReactCoffeeScriptClass', ->
|
||||
test React.createElement(Foo, foo: 'foo'), 'DIV', 'foo bar'
|
||||
undefined
|
||||
|
||||
it 'warns if getDerivedStateFromProps is not static', ->
|
||||
class Foo extends React.Component
|
||||
render: ->
|
||||
div()
|
||||
getDerivedStateFromProps: ->
|
||||
{}
|
||||
expect(->
|
||||
ReactDOM.render(React.createElement(Foo, foo: 'foo'), container)
|
||||
).toWarnDev 'Foo: getDerivedStateFromProps() is defined as an instance method and will be ignored. Instead, declare it as a static method.',
|
||||
undefined
|
||||
|
||||
it 'warns if getDerivedStateFromCatch is not static', ->
|
||||
class Foo extends React.Component
|
||||
render: ->
|
||||
div()
|
||||
getDerivedStateFromCatch: ->
|
||||
{}
|
||||
expect(->
|
||||
ReactDOM.render(React.createElement(Foo, foo: 'foo'), container)
|
||||
).toWarnDev 'Foo: getDerivedStateFromCatch() is defined as an instance method and will be ignored. Instead, declare it as a static method.',
|
||||
undefined
|
||||
|
||||
it 'warns if getSnapshotBeforeUpdate is static', ->
|
||||
class Foo extends React.Component
|
||||
render: ->
|
||||
div()
|
||||
Foo.getSnapshotBeforeUpdate = () ->
|
||||
{}
|
||||
expect(->
|
||||
ReactDOM.render(React.createElement(Foo, foo: 'foo'), container)
|
||||
).toWarnDev 'Foo: getSnapshotBeforeUpdate() is defined as a static method and will be ignored. Instead, declare it as an instance method.',
|
||||
undefined
|
||||
|
||||
it 'warns if state not initialized before static getDerivedStateFromProps', ->
|
||||
class Foo extends React.Component
|
||||
render: ->
|
||||
|
||||
@@ -119,43 +119,6 @@ describe('ReactContextValidator', () => {
|
||||
expect(actualComponentWillUpdate).toEqual({foo: 'def'});
|
||||
});
|
||||
|
||||
it('should not pass previous context to lifecycles', () => {
|
||||
let actualComponentDidUpdate;
|
||||
|
||||
class Parent extends React.Component {
|
||||
getChildContext() {
|
||||
return {
|
||||
foo: this.props.foo,
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
return <Component />;
|
||||
}
|
||||
}
|
||||
Parent.childContextTypes = {
|
||||
foo: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
class Component extends React.Component {
|
||||
componentDidUpdate(...args) {
|
||||
actualComponentDidUpdate = args;
|
||||
}
|
||||
|
||||
render() {
|
||||
return <div />;
|
||||
}
|
||||
}
|
||||
Component.contextTypes = {
|
||||
foo: PropTypes.string,
|
||||
};
|
||||
|
||||
const container = document.createElement('div');
|
||||
ReactDOM.render(<Parent foo="abc" />, container);
|
||||
ReactDOM.render(<Parent foo="def" />, container);
|
||||
expect(actualComponentDidUpdate).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('should check context types', () => {
|
||||
class Component extends React.Component {
|
||||
render() {
|
||||
|
||||
@@ -39,7 +39,7 @@ describe('ReactCreateRef', () => {
|
||||
),
|
||||
).toWarnDev(
|
||||
'Unexpected ref object provided for div. ' +
|
||||
'Use either a ref-setter function or Reacte.createRef().\n' +
|
||||
'Use either a ref-setter function or React.createRef().\n' +
|
||||
' in div (at **)\n' +
|
||||
' in Wrapper (at **)',
|
||||
);
|
||||
@@ -52,7 +52,7 @@ describe('ReactCreateRef', () => {
|
||||
),
|
||||
).toWarnDev(
|
||||
'Unexpected ref object provided for ExampleComponent. ' +
|
||||
'Use either a ref-setter function or Reacte.createRef().\n' +
|
||||
'Use either a ref-setter function or React.createRef().\n' +
|
||||
' in ExampleComponent (at **)\n' +
|
||||
' in Wrapper (at **)',
|
||||
);
|
||||
|
||||
@@ -128,6 +128,49 @@ describe('ReactES6Class', () => {
|
||||
test(<Foo foo="foo" />, 'DIV', 'foo bar');
|
||||
});
|
||||
|
||||
it('warns if getDerivedStateFromProps is not static', () => {
|
||||
class Foo extends React.Component {
|
||||
getDerivedStateFromProps() {
|
||||
return {};
|
||||
}
|
||||
render() {
|
||||
return <div />;
|
||||
}
|
||||
}
|
||||
expect(() => ReactDOM.render(<Foo foo="foo" />, container)).toWarnDev(
|
||||
'Foo: getDerivedStateFromProps() is defined as an instance method ' +
|
||||
'and will be ignored. Instead, declare it as a static method.',
|
||||
);
|
||||
});
|
||||
|
||||
it('warns if getDerivedStateFromCatch is not static', () => {
|
||||
class Foo extends React.Component {
|
||||
getDerivedStateFromCatch() {
|
||||
return {};
|
||||
}
|
||||
render() {
|
||||
return <div />;
|
||||
}
|
||||
}
|
||||
expect(() => ReactDOM.render(<Foo foo="foo" />, container)).toWarnDev(
|
||||
'Foo: getDerivedStateFromCatch() is defined as an instance method ' +
|
||||
'and will be ignored. Instead, declare it as a static method.',
|
||||
);
|
||||
});
|
||||
|
||||
it('warns if getSnapshotBeforeUpdate is static', () => {
|
||||
class Foo extends React.Component {
|
||||
static getSnapshotBeforeUpdate() {}
|
||||
render() {
|
||||
return <div />;
|
||||
}
|
||||
}
|
||||
expect(() => ReactDOM.render(<Foo foo="foo" />, container)).toWarnDev(
|
||||
'Foo: getSnapshotBeforeUpdate() is defined as a static method ' +
|
||||
'and will be ignored. Instead, declare it as an instance method.',
|
||||
);
|
||||
});
|
||||
|
||||
it('warns if state not initialized before static getDerivedStateFromProps', () => {
|
||||
class Foo extends React.Component {
|
||||
static getDerivedStateFromProps(nextProps, prevState) {
|
||||
|
||||
@@ -378,6 +378,56 @@ describe('ReactTypeScriptClass', function() {
|
||||
test(React.createElement(Foo, {foo: 'foo'}), 'DIV', 'foo bar');
|
||||
});
|
||||
|
||||
it('warns if getDerivedStateFromProps is not static', function() {
|
||||
class Foo extends React.Component {
|
||||
getDerivedStateFromProps() {
|
||||
return {};
|
||||
}
|
||||
render() {
|
||||
return React.createElement('div', {});
|
||||
}
|
||||
}
|
||||
expect(function() {
|
||||
ReactDOM.render(React.createElement(Foo, {foo: 'foo'}), container);
|
||||
}).toWarnDev(
|
||||
'Foo: getDerivedStateFromProps() is defined as an instance method ' +
|
||||
'and will be ignored. Instead, declare it as a static method.'
|
||||
);
|
||||
});
|
||||
|
||||
it('warns if getDerivedStateFromCatch is not static', function() {
|
||||
class Foo extends React.Component {
|
||||
getDerivedStateFromCatch() {
|
||||
return {};
|
||||
}
|
||||
render() {
|
||||
return React.createElement('div');
|
||||
}
|
||||
}
|
||||
expect(function() {
|
||||
ReactDOM.render(React.createElement(Foo, {foo: 'foo'}), container);
|
||||
}).toWarnDev(
|
||||
'Foo: getDerivedStateFromCatch() is defined as an instance method ' +
|
||||
'and will be ignored. Instead, declare it as a static method.'
|
||||
);
|
||||
});
|
||||
|
||||
it('warns if getSnapshotBeforeUpdate is static', function() {
|
||||
class Foo extends React.Component {
|
||||
static getSnapshotBeforeUpdate() {
|
||||
}
|
||||
render() {
|
||||
return React.createElement('div', {});
|
||||
}
|
||||
}
|
||||
expect(function() {
|
||||
ReactDOM.render(React.createElement(Foo, {foo: 'foo'}), container);
|
||||
}).toWarnDev(
|
||||
'Foo: getSnapshotBeforeUpdate() is defined as a static method ' +
|
||||
'and will be ignored. Instead, declare it as an instance method.'
|
||||
);
|
||||
});
|
||||
|
||||
it('warns if state not initialized before static getDerivedStateFromProps', function() {
|
||||
class Foo extends React.Component {
|
||||
static getDerivedStateFromProps(nextProps, prevState) {
|
||||
|
||||
@@ -433,6 +433,59 @@ describe('create-react-class-integration', () => {
|
||||
expect(instance.state.foo).toBe('bar');
|
||||
});
|
||||
|
||||
it('warns if getDerivedStateFromProps is not static', () => {
|
||||
const Foo = createReactClass({
|
||||
getDerivedStateFromProps() {
|
||||
return {};
|
||||
},
|
||||
render() {
|
||||
return <div />;
|
||||
},
|
||||
});
|
||||
expect(() =>
|
||||
ReactDOM.render(<Foo foo="foo" />, document.createElement('div')),
|
||||
).toWarnDev(
|
||||
'Component: getDerivedStateFromProps() is defined as an instance method ' +
|
||||
'and will be ignored. Instead, declare it as a static method.',
|
||||
);
|
||||
});
|
||||
|
||||
it('warns if getDerivedStateFromCatch is not static', () => {
|
||||
const Foo = createReactClass({
|
||||
getDerivedStateFromCatch() {
|
||||
return {};
|
||||
},
|
||||
render() {
|
||||
return <div />;
|
||||
},
|
||||
});
|
||||
expect(() =>
|
||||
ReactDOM.render(<Foo foo="foo" />, document.createElement('div')),
|
||||
).toWarnDev(
|
||||
'Component: getDerivedStateFromCatch() is defined as an instance method ' +
|
||||
'and will be ignored. Instead, declare it as a static method.',
|
||||
);
|
||||
});
|
||||
|
||||
it('warns if getSnapshotBeforeUpdate is static', () => {
|
||||
const Foo = createReactClass({
|
||||
statics: {
|
||||
getSnapshotBeforeUpdate: function() {
|
||||
return null;
|
||||
},
|
||||
},
|
||||
render() {
|
||||
return <div />;
|
||||
},
|
||||
});
|
||||
expect(() =>
|
||||
ReactDOM.render(<Foo foo="foo" />, document.createElement('div')),
|
||||
).toWarnDev(
|
||||
'Component: getSnapshotBeforeUpdate() is defined as a static method ' +
|
||||
'and will be ignored. Instead, declare it as an instance method.',
|
||||
);
|
||||
});
|
||||
|
||||
it('should warn if state is not properly initialized before getDerivedStateFromProps', () => {
|
||||
const Component = createReactClass({
|
||||
statics: {
|
||||
@@ -475,7 +528,49 @@ describe('create-react-class-integration', () => {
|
||||
|
||||
expect(() => {
|
||||
ReactDOM.render(<Component />, document.createElement('div'));
|
||||
}).toWarnDev('Defines both componentWillReceiveProps');
|
||||
}).toWarnDev(
|
||||
'Unsafe legacy lifecycles will not be called for components using new component APIs.\n\n' +
|
||||
'Component uses getDerivedStateFromProps() but also contains the following legacy lifecycles:\n' +
|
||||
' componentWillMount\n' +
|
||||
' componentWillReceiveProps\n' +
|
||||
' componentWillUpdate\n\n' +
|
||||
'The above lifecycles should be removed. Learn more about this warning here:\n' +
|
||||
'https://fb.me/react-async-component-lifecycle-hooks',
|
||||
);
|
||||
ReactDOM.render(<Component foo={1} />, document.createElement('div'));
|
||||
});
|
||||
|
||||
it('should not invoke deprecated lifecycles (cWM/cWRP/cWU) if new getSnapshotBeforeUpdate is present', () => {
|
||||
const Component = createReactClass({
|
||||
getSnapshotBeforeUpdate: function() {
|
||||
return null;
|
||||
},
|
||||
componentWillMount: function() {
|
||||
throw Error('unexpected');
|
||||
},
|
||||
componentWillReceiveProps: function() {
|
||||
throw Error('unexpected');
|
||||
},
|
||||
componentWillUpdate: function() {
|
||||
throw Error('unexpected');
|
||||
},
|
||||
componentDidUpdate: function() {},
|
||||
render: function() {
|
||||
return null;
|
||||
},
|
||||
});
|
||||
|
||||
expect(() => {
|
||||
ReactDOM.render(<Component />, document.createElement('div'));
|
||||
}).toWarnDev(
|
||||
'Unsafe legacy lifecycles will not be called for components using new component APIs.\n\n' +
|
||||
'Component uses getSnapshotBeforeUpdate() but also contains the following legacy lifecycles:\n' +
|
||||
' componentWillMount\n' +
|
||||
' componentWillReceiveProps\n' +
|
||||
' componentWillUpdate\n\n' +
|
||||
'The above lifecycles should be removed. Learn more about this warning here:\n' +
|
||||
'https://fb.me/react-async-component-lifecycle-hooks',
|
||||
);
|
||||
ReactDOM.render(<Component foo={1} />, document.createElement('div'));
|
||||
});
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
import type {Deadline} from 'react-reconciler';
|
||||
|
||||
import {alwaysUseRequestIdleCallbackPolyfill} from 'shared/ReactFeatureFlags';
|
||||
import ExecutionEnvironment from 'fbjs/lib/ExecutionEnvironment';
|
||||
import warning from 'fbjs/lib/warning';
|
||||
|
||||
@@ -62,6 +63,7 @@ if (!ExecutionEnvironment.canUseDOM) {
|
||||
timeRemaining() {
|
||||
return Infinity;
|
||||
},
|
||||
didTimeout: false,
|
||||
});
|
||||
});
|
||||
};
|
||||
@@ -69,6 +71,7 @@ if (!ExecutionEnvironment.canUseDOM) {
|
||||
clearTimeout(timeoutID);
|
||||
};
|
||||
} else if (
|
||||
alwaysUseRequestIdleCallbackPolyfill ||
|
||||
typeof requestIdleCallback !== 'function' ||
|
||||
typeof cancelIdleCallback !== 'function'
|
||||
) {
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
import invariant from 'fbjs/lib/invariant';
|
||||
|
||||
// Exports ReactDOM.createRoot
|
||||
export const enableCreateRoot = true;
|
||||
export const enableUserTimingAPI = __DEV__;
|
||||
|
||||
// Mutating mode (React DOM, React ART, React Native):
|
||||
@@ -38,6 +37,8 @@ export const replayFailedUnitOfWorkWithInvokeGuardedCallback = __DEV__;
|
||||
// Warn about deprecated, async-unsafe lifecycles; relates to RFC #6:
|
||||
export const warnAboutDeprecatedLifecycles = false;
|
||||
|
||||
export const alwaysUseRequestIdleCallbackPolyfill = false;
|
||||
|
||||
// Only used in www builds.
|
||||
export function addUserTimingListener() {
|
||||
invariant(false, 'Not implemented.');
|
||||
|
||||
@@ -10,22 +10,23 @@
|
||||
export type TypeOfSideEffect = number;
|
||||
|
||||
// Don't change these two values. They're used by React Dev Tools.
|
||||
export const NoEffect = /* */ 0b00000000000;
|
||||
export const PerformedWork = /* */ 0b00000000001;
|
||||
export const NoEffect = /* */ 0b000000000000;
|
||||
export const PerformedWork = /* */ 0b000000000001;
|
||||
|
||||
// You can change the rest (and add more).
|
||||
export const Placement = /* */ 0b00000000010;
|
||||
export const Update = /* */ 0b00000000100;
|
||||
export const PlacementAndUpdate = /* */ 0b00000000110;
|
||||
export const Deletion = /* */ 0b00000001000;
|
||||
export const ContentReset = /* */ 0b00000010000;
|
||||
export const Callback = /* */ 0b00000100000;
|
||||
export const DidCapture = /* */ 0b00001000000;
|
||||
export const Ref = /* */ 0b00010000000;
|
||||
export const ErrLog = /* */ 0b00100000000;
|
||||
export const Placement = /* */ 0b000000000010;
|
||||
export const Update = /* */ 0b000000000100;
|
||||
export const PlacementAndUpdate = /* */ 0b000000000110;
|
||||
export const Deletion = /* */ 0b000000001000;
|
||||
export const ContentReset = /* */ 0b000000010000;
|
||||
export const Callback = /* */ 0b000000100000;
|
||||
export const DidCapture = /* */ 0b000001000000;
|
||||
export const Ref = /* */ 0b000010000000;
|
||||
export const ErrLog = /* */ 0b000100000000;
|
||||
export const Snapshot = /* */ 0b100000000000;
|
||||
|
||||
// Union of all host effects
|
||||
export const HostEffectMask = /* */ 0b00111111111;
|
||||
export const HostEffectMask = /* */ 0b100111111111;
|
||||
|
||||
export const Incomplete = /* */ 0b01000000000;
|
||||
export const ShouldCapture = /* */ 0b10000000000;
|
||||
export const Incomplete = /* */ 0b001000000000;
|
||||
export const ShouldCapture = /* */ 0b010000000000;
|
||||
|
||||
@@ -62,7 +62,7 @@ export type ReactProvider<T> = {
|
||||
|
||||
export type ReactProviderType<T> = {
|
||||
$$typeof: Symbol | number,
|
||||
context: ReactContext<T>,
|
||||
_context: ReactContext<T>,
|
||||
};
|
||||
|
||||
export type ReactConsumer<T> = {
|
||||
@@ -72,7 +72,7 @@ export type ReactConsumer<T> = {
|
||||
ref: null,
|
||||
props: {
|
||||
children: (value: T) => ReactNodeList,
|
||||
bits?: number,
|
||||
unstable_observedBits?: number,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -8,4 +8,4 @@
|
||||
'use strict';
|
||||
|
||||
// TODO: this is special because it gets imported during build.
|
||||
module.exports = '16.3.0-alpha.2';
|
||||
module.exports = '16.3.1';
|
||||
|
||||
@@ -14,7 +14,6 @@ import typeof * as FabricFeatureFlagsType from './ReactFeatureFlags.native-fabri
|
||||
|
||||
export const debugRenderPhaseSideEffects = false;
|
||||
export const debugRenderPhaseSideEffectsForStrictMode = false;
|
||||
export const enableCreateRoot = false;
|
||||
export const enableUserTimingAPI = __DEV__;
|
||||
export const enableGetDerivedStateFromCatch = false;
|
||||
export const warnAboutDeprecatedLifecycles = false;
|
||||
@@ -24,6 +23,7 @@ export const replayFailedUnitOfWorkWithInvokeGuardedCallback = __DEV__;
|
||||
export const enableMutatingReconciler = false;
|
||||
export const enableNoopReconciler = false;
|
||||
export const enablePersistentReconciler = true;
|
||||
export const alwaysUseRequestIdleCallbackPolyfill = false;
|
||||
|
||||
// Only used in www builds.
|
||||
export function addUserTimingListener() {
|
||||
|
||||
@@ -22,11 +22,11 @@ export const {
|
||||
} = require('ReactFeatureFlags');
|
||||
|
||||
// The rest of the flags are static for better dead code elimination.
|
||||
export const enableCreateRoot = false;
|
||||
export const enableUserTimingAPI = __DEV__;
|
||||
export const enableMutatingReconciler = true;
|
||||
export const enableNoopReconciler = false;
|
||||
export const enablePersistentReconciler = false;
|
||||
export const alwaysUseRequestIdleCallbackPolyfill = false;
|
||||
|
||||
// Only used in www builds.
|
||||
export function addUserTimingListener() {
|
||||
|
||||
@@ -14,7 +14,6 @@ import typeof * as PersistentFeatureFlagsType from './ReactFeatureFlags.persiste
|
||||
|
||||
export const debugRenderPhaseSideEffects = false;
|
||||
export const debugRenderPhaseSideEffectsForStrictMode = false;
|
||||
export const enableCreateRoot = false;
|
||||
export const enableUserTimingAPI = __DEV__;
|
||||
export const enableGetDerivedStateFromCatch = false;
|
||||
export const warnAboutDeprecatedLifecycles = false;
|
||||
@@ -25,6 +24,7 @@ export const replayFailedUnitOfWorkWithInvokeGuardedCallback = __DEV__;
|
||||
export const enableMutatingReconciler = false;
|
||||
export const enableNoopReconciler = false;
|
||||
export const enablePersistentReconciler = true;
|
||||
export const alwaysUseRequestIdleCallbackPolyfill = false;
|
||||
|
||||
// Only used in www builds.
|
||||
export function addUserTimingListener() {
|
||||
|
||||
35
packages/shared/forks/ReactFeatureFlags.test-renderer.js
Normal file
35
packages/shared/forks/ReactFeatureFlags.test-renderer.js
Normal file
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Copyright (c) 2013-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import invariant from 'fbjs/lib/invariant';
|
||||
|
||||
import typeof * as FeatureFlagsType from 'shared/ReactFeatureFlags';
|
||||
import typeof * as PersistentFeatureFlagsType from './ReactFeatureFlags.persistent';
|
||||
|
||||
export const debugRenderPhaseSideEffects = false;
|
||||
export const debugRenderPhaseSideEffectsForStrictMode = false;
|
||||
export const enableUserTimingAPI = __DEV__;
|
||||
export const enableGetDerivedStateFromCatch = false;
|
||||
export const warnAboutDeprecatedLifecycles = false;
|
||||
export const replayFailedUnitOfWorkWithInvokeGuardedCallback = false;
|
||||
export const enableMutatingReconciler = true;
|
||||
export const enableNoopReconciler = false;
|
||||
export const enablePersistentReconciler = false;
|
||||
export const alwaysUseRequestIdleCallbackPolyfill = false;
|
||||
|
||||
// Only used in www builds.
|
||||
export function addUserTimingListener() {
|
||||
invariant(false, 'Not implemented.');
|
||||
}
|
||||
|
||||
// Flow magic to verify the exports of this file match the original version.
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
type Check<_X, Y: _X, X: Y = _X> = null;
|
||||
// eslint-disable-next-line no-unused-expressions
|
||||
(null: Check<PersistentFeatureFlagsType, FeatureFlagsType>);
|
||||
@@ -17,10 +17,10 @@ export const {
|
||||
debugRenderPhaseSideEffectsForStrictMode,
|
||||
warnAboutDeprecatedLifecycles,
|
||||
replayFailedUnitOfWorkWithInvokeGuardedCallback,
|
||||
alwaysUseRequestIdleCallbackPolyfill,
|
||||
} = require('ReactFeatureFlags');
|
||||
|
||||
// The rest of the flags are static for better dead code elimination.
|
||||
export const enableCreateRoot = true;
|
||||
|
||||
// The www bundles only use the mutating reconciler.
|
||||
export const enableMutatingReconciler = true;
|
||||
|
||||
33
packages/shared/isValidElementType.js
Normal file
33
packages/shared/isValidElementType.js
Normal file
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Copyright (c) 2016-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import {
|
||||
REACT_FRAGMENT_TYPE,
|
||||
REACT_ASYNC_MODE_TYPE,
|
||||
REACT_STRICT_MODE_TYPE,
|
||||
REACT_PROVIDER_TYPE,
|
||||
REACT_CONTEXT_TYPE,
|
||||
REACT_FORWARD_REF_TYPE,
|
||||
} from 'shared/ReactSymbols';
|
||||
|
||||
export default function isValidElementType(type: mixed) {
|
||||
return (
|
||||
typeof type === 'string' ||
|
||||
typeof type === 'function' ||
|
||||
// Note: its typeof might be other than 'symbol' or 'number' if it's a polyfill.
|
||||
type === REACT_FRAGMENT_TYPE ||
|
||||
type === REACT_ASYNC_MODE_TYPE ||
|
||||
type === REACT_STRICT_MODE_TYPE ||
|
||||
(typeof type === 'object' &&
|
||||
type !== null &&
|
||||
(type.$$typeof === REACT_PROVIDER_TYPE ||
|
||||
type.$$typeof === REACT_CONTEXT_TYPE ||
|
||||
type.$$typeof === REACT_FORWARD_REF_TYPE))
|
||||
);
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "simple-cache-provider",
|
||||
"description": "A basic cache for React applications",
|
||||
"version": "0.3.0-alpha.2",
|
||||
"version": "0.4.0",
|
||||
"repository": "facebook/react",
|
||||
"files": [
|
||||
"LICENSE",
|
||||
@@ -13,6 +13,6 @@
|
||||
"fbjs": "^0.8.16"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.3.0-alpha.1 || 16.3.0-alpha.2"
|
||||
"react": "^16.3.0-alpha.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
set -e
|
||||
|
||||
if [ -n $GITHUB_TOKEN ]; then
|
||||
if [ -n "$GITHUB_TOKEN" ]; then
|
||||
|
||||
GH_PAGES_DIR=`pwd`/../react-gh-pages
|
||||
GH_PAGES_DIR=$(pwd)/../react-gh-pages
|
||||
echo "machine github.com login reactjs-bot password $GITHUB_TOKEN" >~/.netrc
|
||||
git config --global user.name "Circle CI"
|
||||
git config --global user.email "circle@reactjs.org"
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
set -e
|
||||
|
||||
yarn test --coverage --maxWorkers=2
|
||||
if [ -z $CI_PULL_REQUEST ]; then
|
||||
cat ./coverage/lcov.info | ./node_modules/.bin/coveralls
|
||||
if [ -z "$CI_PULL_REQUEST" ]; then
|
||||
./node_modules/.bin/coveralls < ./coverage/lcov.info
|
||||
fi
|
||||
|
||||
# TODO: should we also track prod code coverage somehow?
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
set -e
|
||||
|
||||
ALL_FILES=`find packages -name '*.js' | grep -v umd/ | grep -v __tests__ | grep -v __mocks__`
|
||||
COUNT_ALL_FILES=`echo "$ALL_FILES" | wc -l`
|
||||
COUNT_WITH_FLOW=`grep '@flow' $ALL_FILES | perl -pe 's/:.+//' | wc -l`
|
||||
ALL_FILES=$(find packages -name '*.js' | grep -v umd/ | grep -v __tests__ | grep -v __mocks__)
|
||||
COUNT_ALL_FILES=$(echo "$ALL_FILES" | wc -l)
|
||||
COUNT_WITH_FLOW=$(grep '@flow' $ALL_FILES | perl -pe 's/:.+//' | wc -l)
|
||||
node scripts/facts-tracker/index.js "flow-files" "$COUNT_WITH_FLOW/$COUNT_ALL_FILES"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
set -e
|
||||
|
||||
if [ -z $CI_PULL_REQUEST ] && [ -n "$BUILD_SERVER_ENDPOINT" ]; then
|
||||
if [ -z "$CI_PULL_REQUEST" ] && [ -n "$BUILD_SERVER_ENDPOINT" ]; then
|
||||
curl \
|
||||
-F "react.development=@build/dist/react.development.js" \
|
||||
-F "react.production.min=@build/dist/react.production.min.js" \
|
||||
@@ -12,9 +12,9 @@ if [ -z $CI_PULL_REQUEST ] && [ -n "$BUILD_SERVER_ENDPOINT" ]; then
|
||||
-F "react-dom-server.browser.production.min=@build/dist/react-dom-server.browser.production.min.js" \
|
||||
-F "results.json=@build/../scripts/rollup/results.json" \
|
||||
-F "commit=$CIRCLE_SHA1" \
|
||||
-F "date=`git log --format='%ct' -1`" \
|
||||
-F "date=$(git log --format='%ct' -1)" \
|
||||
-F "pull_request=false" \
|
||||
-F "token=$BUILD_SERVER_TOKEN" \
|
||||
-F "branch=$CIRCLE_BRANCH" \
|
||||
$BUILD_SERVER_ENDPOINT
|
||||
"$BUILD_SERVER_ENDPOINT"
|
||||
fi
|
||||
|
||||
@@ -2,6 +2,13 @@
|
||||
|
||||
const jestDiff = require('jest-diff');
|
||||
|
||||
function diffString(a, b) {
|
||||
// jest-diff does not currently handle single line strings correctly
|
||||
// The easiest work around is to ensure that both strings are multiline
|
||||
// https://github.com/facebook/jest/issues/5657
|
||||
return jestDiff(a + '\n', b + '\n');
|
||||
}
|
||||
|
||||
function normalizeCodeLocInfo(str) {
|
||||
return str && str.replace(/at .+?:\d+/g, 'at **');
|
||||
}
|
||||
@@ -49,11 +56,11 @@ const createMatcherFor = consoleMethod =>
|
||||
} else if (expectedMessages.length === 1) {
|
||||
errorMessage =
|
||||
'Unexpected warning recorded: ' +
|
||||
jestDiff(normalizedMessage, expectedMessages[0]);
|
||||
diffString(normalizedMessage, expectedMessages[0]);
|
||||
} else {
|
||||
errorMessage =
|
||||
'Unexpected warning recorded: ' +
|
||||
jestDiff([normalizedMessage], expectedMessages);
|
||||
diffString([normalizedMessage], expectedMessages);
|
||||
}
|
||||
|
||||
// Record the call stack for unexpected warnings.
|
||||
|
||||
@@ -54,7 +54,7 @@ const update = async ({cwd, dry, packages, version}) => {
|
||||
json.version = version;
|
||||
}
|
||||
|
||||
if (project !== 'react') {
|
||||
if (project !== 'react' && json.peerDependencies) {
|
||||
let peerVersion = json.peerDependencies.react.replace('^', '');
|
||||
|
||||
// If the previous release was a pre-release version,
|
||||
@@ -78,6 +78,16 @@ const update = async ({cwd, dry, packages, version}) => {
|
||||
if (prerelease) {
|
||||
json.peerDependencies.react += ` || ${version}`;
|
||||
}
|
||||
|
||||
// Update inter-package dependencies as well.
|
||||
// e.g. react-test-renderer depends on react-is
|
||||
if (json.dependencies) {
|
||||
Object.keys(json.dependencies).forEach(dependency => {
|
||||
if (packages.indexOf(dependency) >= 0) {
|
||||
json.dependencies[dependency] = `^${version}`;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
await writeJson(path, json, {spaces: 2});
|
||||
|
||||
@@ -72,7 +72,7 @@ const closureOptions = {
|
||||
|
||||
function getBabelConfig(updateBabelOptions, bundleType, filename) {
|
||||
let options = {
|
||||
exclude: 'node_modules/**',
|
||||
exclude: '/**/node_modules/**',
|
||||
presets: [],
|
||||
plugins: [],
|
||||
};
|
||||
@@ -230,7 +230,9 @@ function getPlugins(
|
||||
// www still needs require('React') rather than require('react')
|
||||
isFBBundle && {
|
||||
transformBundle(source) {
|
||||
return source.replace(/require\(['"]react['"]\)/g, "require('React')");
|
||||
return source
|
||||
.replace(/require\(['"]react['"]\)/g, "require('React')")
|
||||
.replace(/require\(['"]react-is['"]\)/g, "require('ReactIs')");
|
||||
},
|
||||
},
|
||||
// Apply dead code elimination and/or minification.
|
||||
|
||||
@@ -238,7 +238,7 @@ const bundles = [
|
||||
/******* React Is *******/
|
||||
{
|
||||
label: 'react-is',
|
||||
bundleTypes: [NODE_DEV, NODE_PROD, UMD_DEV, UMD_PROD],
|
||||
bundleTypes: [NODE_DEV, NODE_PROD, FB_DEV, FB_PROD, UMD_DEV, UMD_PROD],
|
||||
moduleType: ISOMORPHIC,
|
||||
entry: 'react-is',
|
||||
global: 'ReactIs',
|
||||
|
||||
@@ -38,6 +38,8 @@ const forks = Object.freeze({
|
||||
return 'shared/forks/ReactFeatureFlags.native-fabric.js';
|
||||
case 'react-reconciler/persistent':
|
||||
return 'shared/forks/ReactFeatureFlags.persistent.js';
|
||||
case 'react-test-renderer':
|
||||
return 'shared/forks/ReactFeatureFlags.test-renderer.js';
|
||||
default:
|
||||
switch (bundleType) {
|
||||
case FB_DEV:
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
const path = require('path');
|
||||
const forks = require('./forks');
|
||||
const bundleTypes = require('./bundles').bundleTypes;
|
||||
|
||||
@@ -44,9 +43,9 @@ function getPeerGlobals(externals, moduleType) {
|
||||
|
||||
// Determines node_modules packages that are safe to assume will exist.
|
||||
function getDependencies(bundleType, entry) {
|
||||
const packageJson = require(path.basename(
|
||||
path.dirname(require.resolve(entry))
|
||||
) + '/package.json');
|
||||
// Replaces any part of the entry that follow the package name (like
|
||||
// "/server" in "react-dom/server") by the path to the package settings
|
||||
const packageJson = require(entry.replace(/(\/.*)?$/, '/package.json'));
|
||||
// Both deps and peerDeps are assumed as accessible.
|
||||
return Array.from(
|
||||
new Set([
|
||||
|
||||
@@ -4,442 +4,442 @@
|
||||
"filename": "react.development.js",
|
||||
"bundleType": "UMD_DEV",
|
||||
"packageName": "react",
|
||||
"size": 56098,
|
||||
"gzip": 15374
|
||||
"size": 56109,
|
||||
"gzip": 15450
|
||||
},
|
||||
{
|
||||
"filename": "react.production.min.js",
|
||||
"bundleType": "UMD_PROD",
|
||||
"packageName": "react",
|
||||
"size": 6883,
|
||||
"gzip": 2949
|
||||
"size": 6868,
|
||||
"gzip": 2943
|
||||
},
|
||||
{
|
||||
"filename": "react.development.js",
|
||||
"bundleType": "NODE_DEV",
|
||||
"packageName": "react",
|
||||
"size": 46517,
|
||||
"gzip": 13035
|
||||
"size": 46528,
|
||||
"gzip": 13045
|
||||
},
|
||||
{
|
||||
"filename": "react.production.min.js",
|
||||
"bundleType": "NODE_PROD",
|
||||
"packageName": "react",
|
||||
"size": 5679,
|
||||
"gzip": 2495
|
||||
"size": 5664,
|
||||
"gzip": 2488
|
||||
},
|
||||
{
|
||||
"filename": "React-dev.js",
|
||||
"bundleType": "FB_DEV",
|
||||
"packageName": "react",
|
||||
"size": 45934,
|
||||
"gzip": 12564
|
||||
"size": 45932,
|
||||
"gzip": 12533
|
||||
},
|
||||
{
|
||||
"filename": "React-prod.js",
|
||||
"bundleType": "FB_PROD",
|
||||
"packageName": "react",
|
||||
"size": 13379,
|
||||
"gzip": 3654
|
||||
"size": 13342,
|
||||
"gzip": 3632
|
||||
},
|
||||
{
|
||||
"filename": "react-dom.development.js",
|
||||
"bundleType": "UMD_DEV",
|
||||
"packageName": "react-dom",
|
||||
"size": 603104,
|
||||
"gzip": 139916
|
||||
"size": 621978,
|
||||
"gzip": 143326
|
||||
},
|
||||
{
|
||||
"filename": "react-dom.production.min.js",
|
||||
"bundleType": "UMD_PROD",
|
||||
"packageName": "react-dom",
|
||||
"size": 100029,
|
||||
"gzip": 32160
|
||||
"size": 102120,
|
||||
"gzip": 32529
|
||||
},
|
||||
{
|
||||
"filename": "react-dom.development.js",
|
||||
"bundleType": "NODE_DEV",
|
||||
"packageName": "react-dom",
|
||||
"size": 587111,
|
||||
"gzip": 135658
|
||||
"size": 605975,
|
||||
"gzip": 139071
|
||||
},
|
||||
{
|
||||
"filename": "react-dom.production.min.js",
|
||||
"bundleType": "NODE_PROD",
|
||||
"packageName": "react-dom",
|
||||
"size": 98757,
|
||||
"gzip": 31377
|
||||
"size": 100836,
|
||||
"gzip": 31830
|
||||
},
|
||||
{
|
||||
"filename": "ReactDOM-dev.js",
|
||||
"bundleType": "FB_DEV",
|
||||
"packageName": "react-dom",
|
||||
"size": 607552,
|
||||
"gzip": 137987
|
||||
"size": 627643,
|
||||
"gzip": 141697
|
||||
},
|
||||
{
|
||||
"filename": "ReactDOM-prod.js",
|
||||
"bundleType": "FB_PROD",
|
||||
"packageName": "react-dom",
|
||||
"size": 291741,
|
||||
"gzip": 54620
|
||||
"size": 299593,
|
||||
"gzip": 55668
|
||||
},
|
||||
{
|
||||
"filename": "react-dom-test-utils.development.js",
|
||||
"bundleType": "UMD_DEV",
|
||||
"packageName": "react-dom",
|
||||
"size": 41803,
|
||||
"gzip": 12011
|
||||
"size": 41796,
|
||||
"gzip": 12004
|
||||
},
|
||||
{
|
||||
"filename": "react-dom-test-utils.production.min.js",
|
||||
"bundleType": "UMD_PROD",
|
||||
"packageName": "react-dom",
|
||||
"size": 10649,
|
||||
"gzip": 3963
|
||||
"size": 10641,
|
||||
"gzip": 3955
|
||||
},
|
||||
{
|
||||
"filename": "react-dom-test-utils.development.js",
|
||||
"bundleType": "NODE_DEV",
|
||||
"packageName": "react-dom",
|
||||
"size": 36540,
|
||||
"gzip": 10554
|
||||
"size": 36533,
|
||||
"gzip": 10547
|
||||
},
|
||||
{
|
||||
"filename": "react-dom-test-utils.production.min.js",
|
||||
"bundleType": "NODE_PROD",
|
||||
"packageName": "react-dom",
|
||||
"size": 10193,
|
||||
"gzip": 3854
|
||||
"size": 10185,
|
||||
"gzip": 3848
|
||||
},
|
||||
{
|
||||
"filename": "ReactTestUtils-dev.js",
|
||||
"bundleType": "FB_DEV",
|
||||
"packageName": "react-dom",
|
||||
"size": 37255,
|
||||
"gzip": 10630
|
||||
"size": 37217,
|
||||
"gzip": 10615
|
||||
},
|
||||
{
|
||||
"filename": "react-dom-unstable-native-dependencies.development.js",
|
||||
"bundleType": "UMD_DEV",
|
||||
"packageName": "react-dom",
|
||||
"size": 62625,
|
||||
"gzip": 16444
|
||||
"size": 62617,
|
||||
"gzip": 16436
|
||||
},
|
||||
{
|
||||
"filename": "react-dom-unstable-native-dependencies.production.min.js",
|
||||
"bundleType": "UMD_PROD",
|
||||
"packageName": "react-dom",
|
||||
"size": 11345,
|
||||
"gzip": 3922
|
||||
"size": 11337,
|
||||
"gzip": 3916
|
||||
},
|
||||
{
|
||||
"filename": "react-dom-unstable-native-dependencies.development.js",
|
||||
"bundleType": "NODE_DEV",
|
||||
"packageName": "react-dom",
|
||||
"size": 58187,
|
||||
"gzip": 15163
|
||||
"size": 58179,
|
||||
"gzip": 15156
|
||||
},
|
||||
{
|
||||
"filename": "react-dom-unstable-native-dependencies.production.min.js",
|
||||
"bundleType": "NODE_PROD",
|
||||
"packageName": "react-dom",
|
||||
"size": 10892,
|
||||
"gzip": 3787
|
||||
"size": 10884,
|
||||
"gzip": 3780
|
||||
},
|
||||
{
|
||||
"filename": "ReactDOMUnstableNativeDependencies-dev.js",
|
||||
"bundleType": "FB_DEV",
|
||||
"packageName": "react-dom",
|
||||
"size": 57841,
|
||||
"gzip": 14786
|
||||
"size": 57783,
|
||||
"gzip": 14756
|
||||
},
|
||||
{
|
||||
"filename": "ReactDOMUnstableNativeDependencies-prod.js",
|
||||
"bundleType": "FB_PROD",
|
||||
"packageName": "react-dom",
|
||||
"size": 26798,
|
||||
"gzip": 5389
|
||||
"size": 26739,
|
||||
"gzip": 5365
|
||||
},
|
||||
{
|
||||
"filename": "react-dom-server.browser.development.js",
|
||||
"bundleType": "UMD_DEV",
|
||||
"packageName": "react-dom",
|
||||
"size": 103787,
|
||||
"gzip": 27142
|
||||
"size": 103774,
|
||||
"gzip": 27138
|
||||
},
|
||||
{
|
||||
"filename": "react-dom-server.browser.production.min.js",
|
||||
"bundleType": "UMD_PROD",
|
||||
"packageName": "react-dom",
|
||||
"size": 15320,
|
||||
"gzip": 5874
|
||||
"size": 15306,
|
||||
"gzip": 5871
|
||||
},
|
||||
{
|
||||
"filename": "react-dom-server.browser.development.js",
|
||||
"bundleType": "NODE_DEV",
|
||||
"packageName": "react-dom",
|
||||
"size": 92831,
|
||||
"gzip": 24841
|
||||
"size": 92818,
|
||||
"gzip": 24838
|
||||
},
|
||||
{
|
||||
"filename": "react-dom-server.browser.production.min.js",
|
||||
"bundleType": "NODE_PROD",
|
||||
"packageName": "react-dom",
|
||||
"size": 14957,
|
||||
"gzip": 5715
|
||||
"size": 14943,
|
||||
"gzip": 5712
|
||||
},
|
||||
{
|
||||
"filename": "ReactDOMServer-dev.js",
|
||||
"bundleType": "FB_DEV",
|
||||
"packageName": "react-dom",
|
||||
"size": 95897,
|
||||
"gzip": 24511
|
||||
"size": 95891,
|
||||
"gzip": 24487
|
||||
},
|
||||
{
|
||||
"filename": "ReactDOMServer-prod.js",
|
||||
"bundleType": "FB_PROD",
|
||||
"packageName": "react-dom",
|
||||
"size": 33567,
|
||||
"gzip": 8346
|
||||
"size": 33522,
|
||||
"gzip": 8329
|
||||
},
|
||||
{
|
||||
"filename": "react-dom-server.node.development.js",
|
||||
"bundleType": "NODE_DEV",
|
||||
"packageName": "react-dom",
|
||||
"size": 94799,
|
||||
"gzip": 25398
|
||||
"size": 94786,
|
||||
"gzip": 25396
|
||||
},
|
||||
{
|
||||
"filename": "react-dom-server.node.production.min.js",
|
||||
"bundleType": "NODE_PROD",
|
||||
"packageName": "react-dom",
|
||||
"size": 15781,
|
||||
"gzip": 6016
|
||||
"size": 15767,
|
||||
"gzip": 6013
|
||||
},
|
||||
{
|
||||
"filename": "react-art.development.js",
|
||||
"bundleType": "UMD_DEV",
|
||||
"packageName": "react-art",
|
||||
"size": 401433,
|
||||
"gzip": 87540
|
||||
"size": 420342,
|
||||
"gzip": 91083
|
||||
},
|
||||
{
|
||||
"filename": "react-art.production.min.js",
|
||||
"bundleType": "UMD_PROD",
|
||||
"packageName": "react-art",
|
||||
"size": 89984,
|
||||
"gzip": 27588
|
||||
"size": 92061,
|
||||
"gzip": 28038
|
||||
},
|
||||
{
|
||||
"filename": "react-art.development.js",
|
||||
"bundleType": "NODE_DEV",
|
||||
"packageName": "react-art",
|
||||
"size": 325500,
|
||||
"gzip": 68506
|
||||
"size": 344399,
|
||||
"gzip": 71874
|
||||
},
|
||||
{
|
||||
"filename": "react-art.production.min.js",
|
||||
"bundleType": "NODE_PROD",
|
||||
"packageName": "react-art",
|
||||
"size": 53944,
|
||||
"gzip": 16683
|
||||
"size": 56045,
|
||||
"gzip": 17231
|
||||
},
|
||||
{
|
||||
"filename": "ReactART-dev.js",
|
||||
"bundleType": "FB_DEV",
|
||||
"packageName": "react-art",
|
||||
"size": 330767,
|
||||
"gzip": 67761
|
||||
"size": 350895,
|
||||
"gzip": 71370
|
||||
},
|
||||
{
|
||||
"filename": "ReactART-prod.js",
|
||||
"bundleType": "FB_PROD",
|
||||
"packageName": "react-art",
|
||||
"size": 170087,
|
||||
"gzip": 28743
|
||||
"size": 176846,
|
||||
"gzip": 29583
|
||||
},
|
||||
{
|
||||
"filename": "ReactNativeRenderer-dev.js",
|
||||
"bundleType": "RN_DEV",
|
||||
"packageName": "react-native-renderer",
|
||||
"size": 456733,
|
||||
"gzip": 98665
|
||||
"size": 476799,
|
||||
"gzip": 102295
|
||||
},
|
||||
{
|
||||
"filename": "ReactNativeRenderer-prod.js",
|
||||
"bundleType": "RN_PROD",
|
||||
"packageName": "react-native-renderer",
|
||||
"size": 221774,
|
||||
"gzip": 37892
|
||||
"size": 228487,
|
||||
"gzip": 38740
|
||||
},
|
||||
{
|
||||
"filename": "react-test-renderer.development.js",
|
||||
"bundleType": "NODE_DEV",
|
||||
"packageName": "react-test-renderer",
|
||||
"size": 322620,
|
||||
"gzip": 67474
|
||||
"size": 342803,
|
||||
"gzip": 70878
|
||||
},
|
||||
{
|
||||
"filename": "react-test-renderer.production.min.js",
|
||||
"bundleType": "NODE_PROD",
|
||||
"packageName": "react-test-renderer",
|
||||
"size": 52459,
|
||||
"gzip": 16050
|
||||
"size": 55008,
|
||||
"gzip": 16673
|
||||
},
|
||||
{
|
||||
"filename": "ReactTestRenderer-dev.js",
|
||||
"bundleType": "FB_DEV",
|
||||
"packageName": "react-test-renderer",
|
||||
"size": 327901,
|
||||
"gzip": 66701
|
||||
"size": 349481,
|
||||
"gzip": 70442
|
||||
},
|
||||
{
|
||||
"filename": "react-test-renderer-shallow.development.js",
|
||||
"bundleType": "NODE_DEV",
|
||||
"packageName": "react-test-renderer",
|
||||
"size": 21475,
|
||||
"gzip": 5309
|
||||
"size": 13766,
|
||||
"gzip": 3469
|
||||
},
|
||||
{
|
||||
"filename": "react-test-renderer-shallow.production.min.js",
|
||||
"bundleType": "NODE_PROD",
|
||||
"packageName": "react-test-renderer",
|
||||
"size": 6799,
|
||||
"gzip": 2213
|
||||
"size": 6929,
|
||||
"gzip": 2270
|
||||
},
|
||||
{
|
||||
"filename": "ReactShallowRenderer-dev.js",
|
||||
"bundleType": "FB_DEV",
|
||||
"packageName": "react-test-renderer",
|
||||
"size": 21120,
|
||||
"gzip": 4625
|
||||
"size": 14088,
|
||||
"gzip": 3437
|
||||
},
|
||||
{
|
||||
"filename": "react-noop-renderer.development.js",
|
||||
"bundleType": "NODE_DEV",
|
||||
"packageName": "react-noop-renderer",
|
||||
"size": 19408,
|
||||
"gzip": 5482
|
||||
"size": 19617,
|
||||
"gzip": 5567
|
||||
},
|
||||
{
|
||||
"filename": "react-noop-renderer.production.min.js",
|
||||
"bundleType": "NODE_PROD",
|
||||
"packageName": "react-noop-renderer",
|
||||
"size": 6643,
|
||||
"gzip": 2618
|
||||
"size": 6649,
|
||||
"gzip": 2619
|
||||
},
|
||||
{
|
||||
"filename": "react-reconciler.development.js",
|
||||
"bundleType": "NODE_DEV",
|
||||
"packageName": "react-reconciler",
|
||||
"size": 303935,
|
||||
"gzip": 62924
|
||||
"size": 322717,
|
||||
"gzip": 66224
|
||||
},
|
||||
{
|
||||
"filename": "react-reconciler.production.min.js",
|
||||
"bundleType": "NODE_PROD",
|
||||
"packageName": "react-reconciler",
|
||||
"size": 45644,
|
||||
"gzip": 14042
|
||||
"size": 47723,
|
||||
"gzip": 14603
|
||||
},
|
||||
{
|
||||
"filename": "react-reconciler-reflection.development.js",
|
||||
"bundleType": "NODE_DEV",
|
||||
"packageName": "react-reconciler",
|
||||
"size": 11040,
|
||||
"gzip": 3435
|
||||
"size": 11033,
|
||||
"gzip": 3428
|
||||
},
|
||||
{
|
||||
"filename": "react-reconciler-reflection.production.min.js",
|
||||
"bundleType": "NODE_PROD",
|
||||
"packageName": "react-reconciler",
|
||||
"size": 2416,
|
||||
"gzip": 1068
|
||||
"size": 2408,
|
||||
"gzip": 1062
|
||||
},
|
||||
{
|
||||
"filename": "react-call-return.development.js",
|
||||
"bundleType": "NODE_DEV",
|
||||
"packageName": "react-call-return",
|
||||
"size": 2691,
|
||||
"gzip": 964
|
||||
"size": 2683,
|
||||
"gzip": 958
|
||||
},
|
||||
{
|
||||
"filename": "react-call-return.production.min.js",
|
||||
"bundleType": "NODE_PROD",
|
||||
"packageName": "react-call-return",
|
||||
"size": 979,
|
||||
"gzip": 531
|
||||
"size": 971,
|
||||
"gzip": 525
|
||||
},
|
||||
{
|
||||
"filename": "ReactFabric-dev.js",
|
||||
"bundleType": "RN_DEV",
|
||||
"packageName": "react-native-renderer",
|
||||
"size": 441580,
|
||||
"gzip": 95147
|
||||
"size": 461645,
|
||||
"gzip": 98803
|
||||
},
|
||||
{
|
||||
"filename": "ReactFabric-prod.js",
|
||||
"bundleType": "RN_PROD",
|
||||
"packageName": "react-native-renderer",
|
||||
"size": 205835,
|
||||
"gzip": 35226
|
||||
"size": 212373,
|
||||
"gzip": 35987
|
||||
},
|
||||
{
|
||||
"filename": "react-reconciler-persistent.development.js",
|
||||
"bundleType": "NODE_DEV",
|
||||
"packageName": "react-reconciler",
|
||||
"size": 303255,
|
||||
"gzip": 62652
|
||||
"size": 322036,
|
||||
"gzip": 65980
|
||||
},
|
||||
{
|
||||
"filename": "react-reconciler-persistent.production.min.js",
|
||||
"bundleType": "NODE_PROD",
|
||||
"packageName": "react-reconciler",
|
||||
"size": 44521,
|
||||
"gzip": 13854
|
||||
"size": 46619,
|
||||
"gzip": 14375
|
||||
},
|
||||
{
|
||||
"filename": "react-is.development.js",
|
||||
"bundleType": "NODE_DEV",
|
||||
"packageName": "react-is",
|
||||
"size": 3684,
|
||||
"gzip": 1077
|
||||
"size": 4195,
|
||||
"gzip": 1201
|
||||
},
|
||||
{
|
||||
"filename": "react-is.production.min.js",
|
||||
"bundleType": "NODE_PROD",
|
||||
"packageName": "react-is",
|
||||
"size": 1556,
|
||||
"gzip": 637
|
||||
"size": 1743,
|
||||
"gzip": 684
|
||||
},
|
||||
{
|
||||
"filename": "react-is.development.js",
|
||||
"bundleType": "UMD_DEV",
|
||||
"packageName": "react-is",
|
||||
"size": 3873,
|
||||
"gzip": 1132
|
||||
"size": 4384,
|
||||
"gzip": 1253
|
||||
},
|
||||
{
|
||||
"filename": "react-is.production.min.js",
|
||||
"bundleType": "UMD_PROD",
|
||||
"packageName": "react-is",
|
||||
"size": 1626,
|
||||
"gzip": 701
|
||||
"size": 1807,
|
||||
"gzip": 748
|
||||
},
|
||||
{
|
||||
"filename": "simple-cache-provider.development.js",
|
||||
"bundleType": "NODE_DEV",
|
||||
"packageName": "simple-cache-provider",
|
||||
"size": 5759,
|
||||
"gzip": 1869
|
||||
"size": 5751,
|
||||
"gzip": 1863
|
||||
},
|
||||
{
|
||||
"filename": "simple-cache-provider.production.min.js",
|
||||
"bundleType": "NODE_PROD",
|
||||
"packageName": "simple-cache-provider",
|
||||
"size": 1295,
|
||||
"gzip": 657
|
||||
"size": 1287,
|
||||
"gzip": 650
|
||||
},
|
||||
{
|
||||
"filename": "create-component-with-subscriptions.development.js",
|
||||
@@ -459,15 +459,29 @@
|
||||
"filename": "create-subscription.development.js",
|
||||
"bundleType": "NODE_DEV",
|
||||
"packageName": "create-subscription",
|
||||
"size": 5625,
|
||||
"gzip": 1980
|
||||
"size": 5701,
|
||||
"gzip": 2024
|
||||
},
|
||||
{
|
||||
"filename": "create-subscription.production.min.js",
|
||||
"bundleType": "NODE_PROD",
|
||||
"packageName": "create-subscription",
|
||||
"size": 2653,
|
||||
"gzip": 1254
|
||||
"size": 2634,
|
||||
"gzip": 1259
|
||||
},
|
||||
{
|
||||
"filename": "ReactIs-dev.js",
|
||||
"bundleType": "FB_DEV",
|
||||
"packageName": "react-is",
|
||||
"size": 4231,
|
||||
"gzip": 1200
|
||||
},
|
||||
{
|
||||
"filename": "ReactIs-prod.js",
|
||||
"bundleType": "FB_PROD",
|
||||
"packageName": "react-is",
|
||||
"size": 3382,
|
||||
"gzip": 932
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -9,6 +9,8 @@
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const BatchedBridge = require('BatchedBridge');
|
||||
|
||||
// TODO @sema: Adjust types
|
||||
import type {ReactNativeType} from 'ReactNativeTypes';
|
||||
|
||||
@@ -20,4 +22,6 @@ if (__DEV__) {
|
||||
ReactFabric = require('ReactFabric-prod');
|
||||
}
|
||||
|
||||
BatchedBridge.registerCallableModule('ReactFabric', ReactFabric);
|
||||
|
||||
module.exports = (ReactFabric: ReactNativeType);
|
||||
|
||||
@@ -93,7 +93,6 @@ ${source}`;
|
||||
${license}
|
||||
*
|
||||
* @noflow
|
||||
* @providesModule ${globalName}-dev
|
||||
* @preventMunge
|
||||
*/
|
||||
|
||||
@@ -112,7 +111,6 @@ ${source}
|
||||
${license}
|
||||
*
|
||||
* @noflow
|
||||
* @providesModule ${globalName}-prod
|
||||
* @preventMunge
|
||||
*/
|
||||
|
||||
|
||||
@@ -4941,6 +4941,10 @@ sax@^1.2.1:
|
||||
version "5.3.0"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f"
|
||||
|
||||
semver@^5.5.0:
|
||||
version "5.5.0"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab"
|
||||
|
||||
set-blocking@^2.0.0, set-blocking@~2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
|
||||
@@ -5599,8 +5603,8 @@ whatwg-encoding@^1.0.1:
|
||||
iconv-lite "0.4.13"
|
||||
|
||||
whatwg-fetch@>=0.10.0:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz#9c84ec2dcf68187ff00bc64e1274b442176e1c84"
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz#dde6a5df315f9d39991aa17621853d720b85566f"
|
||||
|
||||
whatwg-url@^6.3.0:
|
||||
version "6.4.0"
|
||||
|
||||
Reference in New Issue
Block a user