* BUG: ReactPartialRenderer / New Context polutes mutable global state
The new context API stores the provided values on the shared context instance. When used in a synchronous context, this is not an issue. However when used in an concurrent context this can cause a "push provider" from one react render to have an effect on an unrelated concurrent react render.
I've encountered this bug in production when using renderToNodeStream, which asks ReactPartialRenderer for bytes up to a high water mark before yielding. If two Node Streams are created and read from in parallel, the state of one can polute the other.
I wrote a failing test to illustrate the conditions under which this happens.
I'm also concerned that the experimental concurrent/async React rendering on the client could suffer from the same issue.
* Use unique thread ID for each partial render to access Context
This first adds an allocator that keeps track of a unique ThreadID index
for each currently executing partial renderer. IDs are not just growing
but are reused as streams are destroyed.
This ensures that IDs are kept nice and compact.
This lets us use an "array" for each Context object to store the current
values. The look up for these are fast because they're just looking up
an offset in a tightly packed "array".
I don't use an actual Array object to store the values. Instead, I rely
on that VMs (notably V8) treat storage of numeric index property access
as a separate "elements" allocation.
This lets us avoid an extra indirection.
However, we must ensure that these arrays are not holey to preserve this
feature.
To do that I store the _threadCount on each context (effectively it takes
the place of the .length property on an array).
This lets us first validate that the context has enough slots before we
access the slot. If not, we fill in the slots with the default value.
* Recover from errors with a boundary in completion phase
* Use a separate field for completing unit of work
* Use a simpler fix with one boolean
* Reoder conditions
* Clarify which paths are DEV-only
* Move duplicated line out
* Make it clearer this code is DEV-only
Setting to null isn't correct; setting to '' is. I opted to use dangerousStyleValue for consistency with the main path that we set things.
Fixes#14114.
Test Plan:
Verified setting to '' works in Chrome and IE11. (Setting to null works in Chrome but not in IE11.)
* Avoid double commit by re-rendering immediately and reusing children
To support Suspense outside of concurrent mode, any component that
starts rendering must commit synchronously without being interrupted.
This means normal path, where we unwind the stack and try again from the
nearest Suspense boundary, won't work.
We used to have a special case where we commit the suspended tree in an
incomplete state. Then, in a subsequent commit, we re-render using the
fallback.
The first part — committing an incomplete tree — hasn't changed with
this PR. But I've changed the second part — now we render the fallback
children immediately, within the same commit.
* Add a failing test for remounting fallback in sync mode
* Add failing test for stuck Suspense fallback
* Toggle visibility of Suspense children in mutation phase, not layout
If parent reads visibility of children in a lifecycle, they should have
already updated.
Check for existence of `setTimeout` and `clearTimeout` in the runtime
before using them, to ensure runtimes without them (like .NET ClearScript)
do not crash just by importing `react-dom`.
Removes the `enableDispatchCallback` feature flag and deletes the
associated code. An earlier version of the Hooks proposal included this
feature but we've since decided to remove it.
* [scheduler] Deadline object -> shouldYield
Instead of using a requestIdleCallback-style deadline object, expose a
method Scheduler.shouldYield that returns true if there's a higher
priority event in the queue.
* Nits
* [react-cache] Remove `cache` as argument to `read`
Updated is API is `Resource.read(key)` instead of
`Resource.read(cache, key)`.
The cache is read from context using `readContext`.
This also removes cache invalidation entirely (other than the default
LRU mechanism), as well as the ability to have multiple caches. We'll
add it back once `Context.write` lands and we can implement it the
right way.
Since there's now only a single cache (the global one), we don't
actually need to use context yet, but I've added a dummy context
anyway so the user gets an error if they attempt to read outside the
render phase.
* nits
* Add test for thenables that resolve multiple times
We are using it with lazy and the combination Suspense + lazy seems pretty
stable. maxDuration is not but that's only enabled when you're in
ConcurrentMode which is still unstable.
* Add support for React.pure in ReactDOMServer
* Unwrap pure wrappers by creating an additional element as a single child
This is very slow but meh. We're rewriting this whole thing anyway.
* Introduce elementType field
This will be used to store the wrapped type of an element. E.g. pure and
lazy.
The existing type field will be used for the unwrapped type within them.
* Store the unwrapped type on the type field of lazy components
* Use the raw tags for lazy components
Instead, we check if the elementType and type are equal to test if
we need to resolve props. This is slightly slower in the normal case
but will yield less code and branching.
* Clean up lazy branches
* Collapse work tag numbering
* Split IndeterminateComponent out from Lazy
This way we don't have to check the type in a hacky way in the
indeterminate path. Also, lets us deal with lazy that resolves to
indeterminate and such.
* Missing clean up in rebase
Removes support for using arbitrary promises as the type of a React
element. Instead, promises must be wrapped in React.lazy. This gives us
flexibility later if we need to change the protocol.
The reason is that promises do not provide a way to call their
constructor multiple times. For example:
const promiseForA = new Promise(resolve => {
fetchA(a => resolve(a));
});
Given a reference to `promiseForA`, there's no way to call `fetchA`
again. Calling `then` on the promise doesn't run the constructor again;
it only attaches another listener.
In the future we will likely introduce an API like `React.eager` that
is similar to `lazy` but eagerly calls the constructor. That gives us
the ability to call the constructor multiple times. E.g. to increase
the priority, or to retry if the first operation failed.