Compare commits

...

75 Commits

Author SHA1 Message Date
Dan Abramov
dc059579c3 Update bundle sizes for 16.3.1 release 2018-04-04 01:33:06 +01:00
Dan Abramov
787b343f67 Updating package versions for release 16.3.1 2018-04-04 01:22:30 +01:00
Dan Abramov
2279843ef9 Updating yarn.lock file for 16.3.1 release 2018-04-04 01:20:48 +01:00
Dan Abramov
a2cc3c38e2 Follow up: make new warning less wordy (#12532) 2018-04-03 21:56:21 +01:00
Dan Abramov
36c2939372 Improve not-yet-mounted setState warning (#12531)
* Tweak not-yet-mounted setState warning

* Add \n\n
2018-04-03 21:22:44 +01:00
Andrew Clark
0f2f90bd9a getDerivedStateFrom{Props,Catch} should update updateQueue.baseState (#12528)
Based on a bug found in UFI2.

There have been several bugs related to the update queue (and
specifically baseState) recently, so I'm going to follow-up with some
refactoring to clean it up. This is a quick fix so we can ship a
patch release.
2018-04-03 13:02:46 -07:00
Dan Abramov
da4e85567b Remove @providesModule in www bundles (#12529) 2018-04-03 20:12:29 +01:00
Brian Vaughn
eb6e752cab Bumped create-subscription package version (#12526) 2018-04-03 11:06:52 -07:00
Mateusz Burzyński
ba245f6f9b Prefix _context property on returned ReactContext from createContext - it's private (#12501) 2018-04-03 01:47:25 +01:00
Andrew Clark
6f2ea73978 Extract throw to separate function so performUnitOfWork does not deopt (#12521)
Only affects DEV mode, but still important I think.
2018-04-03 01:45:52 +01:00
Dan Abramov
4ccf58a94d Fix context stack misalignment caused by error replay (#12508)
* Add regression tests for error boundary replay bugs

* Ensure the context stack is aligned if renderer throws

* Always throw when replaying a failed unit of work

Replaying a failed unit of work should always throw, because the render
phase is meant to be idempotent, If it doesn't throw, rethrow the
original error, so React's internal stack is not misaligned.

* Reset originalReplayError after replaying

* Typo fix
2018-04-03 00:08:30 +01:00
Flarnie Marchan
7a27ebd52a Update user timing to record when we are about to commit (#12384)
* Update user timing to record when we are about to commit

**what is the change?:**
After repeatedly logging '(React Tree Reconciliation)' we vary the
message slightly for the last reconciliation, which happens right before
we commit.

**why make this change?:**
When debugging performance in the devtools it will be helpful if we can
quickly see where the 'commit' happens in a potentially long list of
sliced '(React Tree Reconciliation)' logs.

**test plan:**
Built and ran one of the fixtures. Also ran the unit test.

(Flarnie will insert a screenshot)

* Ran prettier

* Fixes in response to code review

* Update snapshot tests

* Move isWorking assignment out of branches to top

* Stricter type for stopWorkLoopTimer args
2018-04-02 15:27:33 -07:00
Dan Abramov
6b99c6f9d3 Add missing changelog item 2018-04-02 16:07:16 +01:00
Dan Abramov
59dac9d7a6 Fix DEV performance regression by avoiding Object.assign on Fibers (#12510)
* Fix DEV performance regression by avoiding Object.assign on Fibers

* Reduce allocations in hot path by reusing the stash

Since performUnitOfWork() is not reentrant, it should be safe to reuse the same stash every time instead of creating a new object.
2018-04-01 19:10:37 +01:00
heikkilamarko
0c80977061 Validate React.Fragment props without Map. (#12504) 2018-04-01 01:14:36 +01:00
Minh Nguyen
fa8e67893f Change create-subscription's peerDep on react to ^16.3.0 (#12496) 2018-03-30 14:49:34 -07:00
Dan Abramov
59b39056d9 Fix method name in changelog 2018-03-29 23:27:06 +01:00
Dan Abramov
18ba36d891 Move context API in Changelog to "React" section 2018-03-29 23:19:53 +01:00
Dan Abramov
43044757e5 Fix links 2018-03-29 22:08:20 +01:00
Dan Abramov
2c3f5fb97b Add React 16.3.0 changelog (#12488) 2018-03-29 21:56:45 +01:00
Brian Vaughn
8e3d94ffa1 Update bundle sizes for 16.3.0 release 2018-03-29 13:07:12 -07:00
Brian Vaughn
9778873143 Updating dependencies for react-noop-renderer 2018-03-29 13:03:33 -07:00
Brian Vaughn
b2379d4cbe Updating package versions for release 16.3.0 2018-03-29 13:03:33 -07:00
Brian Vaughn
6294b67a40 unstable_createRoot (#12487)
* Removed enableCreateRoot flag. Renamed createRoot to unstable_createRoot

* ReactDOMRoot test is no longer internal
2018-03-29 12:51:34 -07:00
Dan Abramov
8650d2a135 Disable createRoot for open source builds (#12486) 2018-03-29 20:25:20 +01:00
Brian Vaughn
53fdc19df0 Updated react-is README to show new isValidElementType() 2018-03-29 11:46:18 -07:00
James Reggio
96fe3b1be2 Add React.isValidElementType() (#12483)
* Add React.isValidElementType()

Per the conversation on #12453, there are a number of third-party
libraries (particularly those that generate higher-order components)
that are performing suboptimal validation of element types.

This commit exposes a function that can perform the desired check
without depending upon React internals.

* Move isValidElementType to shared/
2018-03-29 11:45:41 -07:00
Flarnie Marchan
125dd16ba0 Update user timing to record the timeout deadline with 'waiting' events (#12479)
* Update user timing to record the timeout deadline with 'waiting' events

**what is the change?:**
When we are processing work during reconciliation, we have a "timeout"
deadline to finish the work. It's a safety measure that forces things to
finish up synchronously if they are taking too long.

The "timeout" is different depending on the type of interaction which
triggered the reconciliation. We currently have a shorter "timeout" for
"interactive updates", meaning we will try to finish work faster if the
reconciliation was triggered by a click or other user interaction.

For collecting more data in our logs we want to differentiate the
'waiting for async callback...' events based on the "timeout" so I'm
adding that to the logging.

One interesting note - in one of the snapshot tests the "timeout" was
super high. Going to look into that.

**why make this change?:**
Right now we are debugging cases where an interaction triggers a
reconciliation and the "waiting for async callback...' events are too
long, getting blocked because the main thread is too busy. We are
keeping logs of these user timing events and want to filter to focus on
the reconciliation triggered by interaction.

**test plan:**
Manually tested and also updated snapshot tests.

(Flarnie will insert a screenshot)

* Improve wording of message

* ran prettier
2018-03-29 11:26:11 -07:00
Dustan Kasten
15e3dffb4c Don't bail out on referential equality of Consumer's props.children function (#12470)
* Test case for React Context bailing out unexpectedly

* This is 💯% definitely not the correct fix at all

* Revert "This is 💯% definitely not the correct fix at all"

This reverts commit 8686c0f6bdc1cba3056fb2212f3f7740c749d33a.

* Formatting + minor tweaks to the test

* Don't bail out on consumer child equality

* Tweak the comment

* Pretty lint

* Silly Dan
2018-03-29 19:16:02 +01:00
Sophie Alpert
5855e9f215 Improve warning message for setState-on-unmounted (#12347)
This is one of the most common warnings people see, and I don't think the old text is especially clear. Improve it.
2018-03-29 16:21:22 +01:00
Dan Abramov
7a833dad95 setState() in componentDidMount() should flush synchronously even with createBatch() (#12466)
* Add a failing test for setState in cDM during batch.commit()

* Copy pasta

* Flush all follow-up Sync work on the committed batch

* Nit: Use performSyncWork

Call performSyncWork right after flushing the batch. Does effectively
the same thing by reusing the existing function.

Also added some comments.

* Delete accidentally duplicated test
2018-03-29 02:41:42 +01:00
Andrew Clark
c44665e832 Fix bug when fatal error is thrown as a result of batch.commit (#12480)
Fixes #12474
2018-03-28 18:18:09 -07:00
Andrew Clark
268a3f60df Add unstable APIs for async rendering to test renderer (#12478)
These are based on the ReactNoop renderer, which we use to test React
itself. This gives library authors (Relay, Apollo, Redux, et al.) a way
to test their components for async compatibility.

- Pass `unstable_isAsync` to `TestRenderer.create` to create an async
renderer instance. This causes updates to be lazily flushed.
- `renderer.unstable_yield` tells React to yield execution after the
currently rendering component.
- `renderer.unstable_flushAll` flushes all pending async work, and
returns an array of yielded values.
- `renderer.unstable_flushThrough` receives an array of expected values,
begins rendering, and stops once those values have been yielded. It
returns the array of values that are actually yielded. The user should
assert that they are equal.

Although we've used this pattern successfully in our own tests, I'm not
sure if these are the final APIs we'll make public.
2018-03-28 14:57:25 -07:00
Brian Vaughn
c1b21a746c Added DEV warning if getSnapshotBeforeUpdate is defined as a static method (#12475) 2018-03-28 13:35:32 -07:00
Nikolay
488ad5a6b9 Fix typo in create-subscription readme
PR: #12473
2018-03-28 08:51:16 -04:00
Brian Vaughn
c2c3c0cc36 Fix build script to handle react-is (no peer deps) (#12471) 2018-03-27 19:19:34 -07:00
Brian Vaughn
b3d883630c Update bundle sizes for 16.3.0-rc.0 release 2018-03-27 19:11:20 -07:00
Brian Vaughn
80ddd15b72 Updating dependencies for react-noop-renderer 2018-03-27 19:07:53 -07:00
Brian Vaughn
61444a415b Updating package versions for release 16.3.0-rc.0 2018-03-27 19:07:53 -07:00
Andrew Clark
ff32420e57 Caveat about async in create-subscription README (#12469)
* Caveat about async in create-subscription README

* Address Sophie's comments

* Dan's nits
2018-03-27 16:50:12 -07:00
Brian Vaughn
ad5273d348 Call getSnapshotBeforeUpdate before mutation (#12468)
* Call getSnapshotBeforeUpdate in separate traversal, before mutation (aka revert db84b9a) and add unit test.

* Added a new timer to ReactDebugFiberPerf for Snapshot effects
2018-03-27 15:37:13 -07:00
Brian Vaughn
90c41a2e56 Rename react-is import alias in FB bundles (#12459) 2018-03-27 08:57:11 -07:00
Brian Vaughn
718d0d21f2 Include react-is in FB build targets (#12458) 2018-03-26 16:56:06 -07:00
Brian Vaughn
e1a106a071 New commit phase lifecycle: getSnapshotBeforeUpdate (#12404)
* Implemented new getSnapshotBeforeUpdate lifecycle
* Store snapshot value from Fiber to instance (__reactInternalSnapshotBeforeUpdate)
* Use commitAllHostEffects() traversal for getSnapshotBeforeUpdate()
* Added DEV warnings and tests for new lifecycle
* Don't invoke legacy lifecycles if getSnapshotBeforeUpdate() is defined. DEV warn about this.
* Converted did-warn objects to Sets in ReactFiberClassComponent
* Replaced redundant new lifecycle checks in a few methods
* Check for polyfill suppress flag on cWU as well before warning
* Added Snapshot bit to HostEffectMask
2018-03-26 13:28:10 -07:00
Brian Vaughn
e9ba8ec866 Workaround jest-diff single line string limitation (#12456) 2018-03-26 10:48:36 -07:00
Jason Quense
dadafd6bd8 Remove dependency on React (#12448)
Is this necessary? I'd like to use the package in enzyme to avoid having to recopy/paste the symbols for better debugging names, but at hard dep in enzyme proper on a version of react isn't gonna work. This seems safe since nothing explicitly depends on React in here?
2018-03-24 16:59:36 +00:00
Dan Abramov
7d31311de3 Don't pass a Fiber to showErrorDialog() (#12445)
* Don't pass a Fiber to showErrorDialog()

* Only fill in the fields for classes

* Reorder for clarity
2018-03-23 21:52:53 +00:00
Maël Nison
1bab82a9de Tweaks the build script (#12444)
Branch: build-tweaks
2018-03-23 19:51:04 +00:00
Maël Nison
cc616b01fc Adds semver to the package dev dependencies (#12442)
Branch: semver
2018-03-23 19:31:16 +00:00
Rene Hangstrup Møller
1a71c4de13 Rename bits to unstable_observedBits (#12440) 2018-03-23 15:58:49 +00:00
Brian Vaughn
cafee5cb2f Update bundle sizes for 16.3.0-alpha.3 release 2018-03-22 12:45:10 -07:00
Brian Vaughn
3cdb5780d4 Updating dependencies for react-noop-renderer 2018-03-22 12:41:43 -07:00
Brian Vaughn
02d4e5dd39 Updating package versions for release 16.3.0-alpha.3 2018-03-22 12:41:43 -07:00
Brian Vaughn
8c20615b06 Removed dev warnings from shallow renderer. (#12433) 2018-03-22 11:32:37 -07:00
Brian Vaughn
c1308adb4b Expanded DEV-only warnings for gDSFP and legacy lifecycles (#12419) 2018-03-22 11:16:54 -07:00
Dan Abramov
0af384b4c3 Warn about non-static getDerivedStateFromProps/Catch (#12431) 2018-03-22 17:54:51 +00:00
Dan Abramov
12687ff331 Use "Component" as fallback name in more places (#12430) 2018-03-22 17:26:46 +00:00
Dan Abramov
dcbb4301f0 Add a fallback component name for warnings (#12429) 2018-03-22 17:22:13 +00:00
Brian Vaughn
40fa616053 Subscriptions shouldn't call setState after unmount even for Promises (#12425) 2018-03-22 08:54:57 -07:00
Rajendra arora
f94a6b4fed Removed documentation badge from readme.md (#12424)
* Added badge for react documentation

* Updated reference documentation link for badge

* Update README.md

* Update README.md

* Update README.md

* Removed reference badge from Readme.md
2018-03-22 15:48:52 +00:00
Brian Vaughn
dc48326cd5 Fixed a batched-state update bug with getDerivedStateFromProps (#12408) 2018-03-21 11:42:52 -07:00
Rajendra arora
c6b7cea343 Added badge for react documentation (#12191)
* Added badge for react documentation

* Updated reference documentation link for badge
2018-03-21 13:05:02 -04:00
Dan Abramov
3553489f7b Fix now-missing errorInfo argument to componentDidCatch() (#12416)
* Add a failing test verifying componentInfo is missing

* Pass componentInfo to componentDidCatch and getDerivedStateFromCatch

* Only expect stack in DEV

* Don't pass the stack to getDerivedStateFromCatch()
2018-03-21 16:38:24 +00:00
Léo Andrès
3ed6483e14 Clean shell scripts (#12365) 2018-03-21 12:03:09 -04:00
Barry Michael Doyle
f9377c1762 Replaced object building loop with Object.assign function (#12414) 2018-03-21 09:45:45 -04:00
Vasiliy
33eddbc0c8 Fix falling in dev mode (#12407)
FiberNode stateNode could be null

So I get TypeError:

```
  at performWorkOnRoot (/tmp/my-project/node_modules/react-dom/cjs/react-dom.development.js:11014:24) TypeError: Cannot read property '_warnedAboutRefsInRender' of null
          at findDOMNode (/tmp/my-project/node_modules/react-dom/cjs/react-dom.development.js:15264:55)
```
2018-03-21 09:41:50 +00:00
Dan Abramov
ab4dc50146 Fix Prettier 2018-03-21 09:41:23 +00:00
Kevin Gozali
9d484edc4b [fabric] register ReactFabric to be callable module (#12405) 2018-03-20 14:56:18 +00:00
Dan Abramov
8d09422424 Fix an infinite loop in new context (#12402)
* Add a regression test for the context infinite loop

* Fix the bug

We set .return pointer inside the loop, but the top-level parent-child relationship happens outside.

This ensures the top-level parent's child points to the right copy of the parent.

Otherwise we may end up in a situation where (workInProgress === nextFiber) is never true and we loop forever.
2018-03-20 13:06:59 +00:00
Jason Quense
e1ff342bf7 Support ForwardRef type of work in TestRenderer (#12392)
* Support ForwardRef type of work in TestRenderer and ShallowRenderer.
* Release script now updates inter-package dependencies too (e.g. react-test-renderer depends on react-is).
2018-03-16 11:18:50 -07:00
Andrew Clark
7e87df8090 Feature flag: Use custom requestIdleCallback even when native one exists (#12385)
We'll use this in www to test whether the polyfill is better at
scheduling high-pri async work than the native one. My preliminary tests
suggest "yes" but it's hard to say for certain, given how difficult it
is to consistently reproduce the starvation issues we've been seeing.
2018-03-15 19:28:21 -07:00
Andrew Clark
208b490ed9 Unify context stack implementations (#12359)
* Use module pattern so context stack is isolated per renderer

* Unify context implementations

Implements the new context API on top of the existing ReactStack that we
already use for host context and legacy context. Now there is a single
array that we push and pop from.

This makes the interrupt path slightly slower, since when we reset the
unit of work pointer, we have to iterate over the stack (like before)
*and* switch on the type of work (not like before). On the other hand,
this unifies all of the unwinding behavior in the UnwindWork module.

* Add DEV only warning if stack is not reset properly
2018-03-15 19:27:44 -07:00
Brian Vaughn
2738e84805 Removed an unnecessary wrapper object from state (#12383)
* Removed an unnecessary wrapper object from state
* Moved unsubscribe from state to class field and tweaked comments
2018-03-15 11:43:01 -07:00
Roman Hotsiy
d38616d693 Fix typo in unexpected ref object warning (#12377) 2018-03-15 12:30:50 +00:00
Brian Vaughn
ced176edb7 Updated create-subscription description 2018-03-14 15:39:38 -07:00
91 changed files with 3263 additions and 1358 deletions

View File

@@ -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)

View File

@@ -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",

View File

@@ -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";

View File

@@ -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"

View File

@@ -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', () => {

View File

@@ -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;
}
}

View File

@@ -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",

View File

@@ -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"
}
}

View File

@@ -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",

View File

@@ -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);
});
});

View File

@@ -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);

View File

@@ -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();

View File

@@ -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');
});
});

View File

@@ -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');
});
});

View File

@@ -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,

View File

@@ -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);
}

View File

@@ -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 = [];

View File

@@ -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;

View File

@@ -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 {

View File

@@ -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";

View File

@@ -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",

View File

@@ -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;
}

View File

@@ -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,

View File

@@ -23,6 +23,7 @@ let frameDeadline: number = 0;
const frameDeadlineObject: Deadline = {
timeRemaining: () => frameDeadline - now(),
didTimeout: false,
};
function setTimeoutCallback() {

View File

@@ -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"

View File

@@ -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) {

View File

@@ -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",

View File

@@ -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) {

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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.

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,
};
}

View File

@@ -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,
};
}

View File

@@ -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,
};
}

View File

@@ -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,

View File

@@ -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,
};
}

View File

@@ -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,
};
}

View File

@@ -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,
};
}

View File

@@ -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);
}
};

View File

@@ -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')]);
});
});

View File

@@ -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);
});
});

View 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');
});
});

View File

@@ -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', () => {

View File

@@ -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')]);
});
});

View File

@@ -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', () => {

View File

@@ -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)
"

View File

@@ -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",

View File

@@ -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);

View File

@@ -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;

View File

@@ -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 />);
});
});
});

View File

@@ -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 = [];

View File

@@ -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,
}),
);
});
});

View 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']);
});
});

View File

@@ -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",

View File

@@ -44,7 +44,7 @@ export function createContext<T>(
context.Provider = {
$$typeof: REACT_PROVIDER_TYPE,
context,
_context: context,
};
context.Consumer = context;

View File

@@ -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.

View File

@@ -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,
);

View File

@@ -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: ->

View File

@@ -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() {

View File

@@ -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 **)',
);

View File

@@ -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) {

View File

@@ -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) {

View File

@@ -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'));
});

View File

@@ -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'
) {

View File

@@ -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.');

View File

@@ -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;

View File

@@ -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,
},
};

View File

@@ -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';

View File

@@ -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() {

View File

@@ -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() {

View File

@@ -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() {

View 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>);

View File

@@ -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;

View 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))
);
}

View File

@@ -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"
}
}

View File

@@ -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"

View File

@@ -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?

View File

@@ -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"

View File

@@ -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

View File

@@ -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.

View File

@@ -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});

View File

@@ -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.

View File

@@ -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',

View File

@@ -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:

View File

@@ -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([

View File

@@ -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
}
]
}

View File

@@ -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);

View File

@@ -93,7 +93,6 @@ ${source}`;
${license}
*
* @noflow
* @providesModule ${globalName}-dev
* @preventMunge
*/
@@ -112,7 +111,6 @@ ${source}
${license}
*
* @noflow
* @providesModule ${globalName}-prod
* @preventMunge
*/

View File

@@ -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"