The `version` field exported by the React package currently corresponds
to the `@next` release for that build. This updates the build script
to output the same version that is used in the package.json file.
It works by doing a find-and-replace of the React version after the
build has completed. This is a bit weird but it saves us from having
to build the `@next` and `@latest` releases separately; they are
identical except for the version numbers.
We don't have strong guarantees that the props object is referentially
equal during updates where we can't bail out anyway — like if the props
are shallowly equal, but there's a local state or context update in the
same batch.
However, as a principle, we should aim to make the behavior consistent
across different ways of memoizing a component. For example, React.memo
has a different internal Fiber layout if you pass a normal function
component (SimpleMemoComponent) versus if you pass a different type like
forwardRef (MemoComponent). But this is an implementation detail.
Wrapping a component in forwardRef (or React.lazy, etc) shouldn't affect
whether the props object is reused during a bailout.
Co-authored-by: Mateusz Burzyński <mateuszburzynski@gmail.com>
During an urgent update, useDeferredValue should reuse the previous
value. The regression test I added shows that it was reverting to
the initial value instead.
The cause of the bug was trivial: the update path doesn't update the
hook's `memoizedState` field. Only the mount path.
None of the existing tests happened to catch this because to trigger the
bug, you have to do an urgent update that isn't the first update after
initial render. In all of the existing tests that included an urgent
update, it was the first update, so the "previous" value and the initial
value happened to be the same thing.
* Add failing test case for #24384
If a components suspends during hydration we expect there to be mismatches with server rendered HTML but we were not always supressing warning messages related to these expected mismatches
* Mark hydration as suspending on every thrownException
previously hydration would only be marked as supsending when a genuine error was thrown. This created an opportunity for a hydration mismatch that would warn after which later hydration mismatches would not lead to warnings. By moving the marker check earlier in the thrownException function we get the hydration context to enter the didSuspend state on both error and thrown promise cases which eliminates this gap.
* Fix failing test related to client render fallbacks
This test was actually subject to the project identified in the issue fixed in this branch. After fixing the underlying issue the assertion logic needed to change to pick the right warning which now emits after hydration successfully completes on promise resolution. I changed the container type to 'section' to make the error message slightly easier to read/understand (for me)
* Only mark didSuspend on suspense path
For unknown reasons the didSuspend was being set only on the error path and nto the suspense path. The original change hoisted this to happen on both paths. This change moves the didSuspend call to the suspense path only. This appears to be a noop because if the error path marked didSuspend it would suppress later warnings but if it does not the warning functions themsevles do that suppression (after the first one which necessarily already happened)
* gate test on hydration fallback flags
* refactor didSuspend to didSuspendOrError
the orignial behavior applied the hydration warning bailout to error paths only. originally I moved it to Suspense paths only but this commit restores it to both paths and renames the marker function as didThrow rather than didSuspend
The logic here is that for either case if we get a mismatch in hydration we want to warm up components but effectively consider the hydration for this boundary halted
* factor tests to assert different behavior between prod and dev
* add DEV suffix to didSuspendOrError to better indicate this feature should only affect dev behavior
* move tests back to ReactDOMFizzServer-test
* fix comment casing
* drop extra flag gates in tests
* add test for user error case
* remove unnecessary gate
* Make test better
it now has an intentional client mismatch that would error if there wasn't suppression brought about by the earlier error. when it client renders it has the updated value not found in the server response but we do not see a hydration warning because it was superseded by the thrown error in that render
* Bug: Missing unmount when suspended tree deleted
When a suspended tree switches to a fallback, we unmount the effects.
If the suspended tree is then deleted, there's a guard to prevent us
from unmounting the effects again.
However, in legacy mode, we don't unmount effects when a tree suspends.
So if the suspended tree is then deleted, we do need to unmount
the effects.
We're missing a check for legacy/concurrent mode.
* Fix: Unmount suspended tree when it is deleted
The previous escape was for Text into HTML and breaks script contents. The new escaping ensures that the script contents cannot prematurely close the host script tag by escaping script open and close string sequences using a unicode escape substitution.
Fixes#24302 based on #24306.
---
The current implementation for strict mode double logging stringiness and dims the second log. However, because we stringify everything, including objects, this causes objects to be logged as `[object Object]` etc.
This PR creates a new function that formats console log arguments with a specified style. It does this by:
1. The first param is a string that contains %c: Bail out and return the args without modifying the styles. We don't want to affect styles that the developer deliberately set.
2. The first param is a string that doesn't contain %c but contains string formatting: `[`%c${args[0]}`, style, ...args.slice(1)]` Note: we assume that the string formatting that the developer uses is correct.
3. The first param is a string that doesn't contain string formatting OR is not a string: Create a formatting string where:
- boolean, string, symbol -> %s
- number -> %f OR %i depending on if it's an int or float
- default -> %o
---
Co-authored-by: Billy Janitsch <billy@kensho.com>
The shell package wasn't compiling because yarn build-for-devtools was incorrect. The react-dom/test package was renamed to react-dom/unstable_testing. This PR fixes this in the package.json.
Note: Adding packages to the yarn build-for-devtools command isn't great in the long run. Eventually we should make devtools have its own build script.
I changed the type of this functions returned value but forgot to change
the check.
It happens to work before anyway, because eventually the interleaved
updates will get transferred at the beginning of the next render phase.
But this is more logically consistent.
* Regression test: Interleaved update race condition
Demonstrates the bug reported in #24350.
* Bugfix: Last update wins, even if interleaved
"Interleaved" updates are updates that are scheduled while a render is
already in progress. We put these on a special queue so that they don't
get processed during the current render. Then we transfer them to
the "real" queue after the render has finished.
There was a race condition where an update is received after the render
has finished but before the interleaved update queue had been
transferred, causing the updates to be queued in the wrong order.
The fix I chose is to check if the interleaved updates queue is empty
before adding any update to the real queue. If it's not empty, then
the new update must also be treated as interleaved.
Adds an Offscreen Queue. We use the offscreen queue to store not yet processed transitions. During the commit phase, we will add these transitions to the transitions field in memoizedState (in the subsequent PR) and clear the transitions field in the updateQueue
Fix matching in the build script.
It's possible to provide a custom bundle name in the case we build deep
imports. We should match those names as a convenience.
The script also calls toLowerCase on requested names but some entries have
upper case now.
In this PR we:
Add transitions boilerplate to the OffscreenState. The transitions field will be null on initiation. During the commit phase, if there are any new transitions, we will add any new transitions (either as a result of a transition occurring or a parent suspense boundary completing) to the transitions field. Once the suspense boundary resolves, we no longer need to store the transitions on the boundary, so we can put this field on the Offscreen memoized state
Add pendingSuspenseBoundaries boilerplate to the RootState. This field starts as null. During the commit phase, if a suspense boundary has either gone from fallback to resolved or from resolved to fallback, we will create a new Map if there isn't one, and if there is, we will add (if the boundary is a fallback) or remove the suspense boundary (if the boundary has resolved) from the map.
Add an optional name field to the Suspense boundary
Previously, we were only adding the passive flag when we add the Visibility flag, which is only set when we go from primary to fallback. Now, we add the passive flag BOTH when we go from primary to fallback and from fallback to primary.
An alternate solution is to add the passive flag in the same place as the visibility flag in the offscreen complete phase (rather than the suspense complete phase), but this feature is currently only for suspense, and offscreen can be used in different ways, so for now we add the passive flag only in the suspense component's complete phase. We might want to revisit this later when we think about how offscreen should work with transition tracing.
We forgot to move pushTransition out from the enableCache flag in #24321 in a place that both transition tracing and cache need to push transitions. Move it out from behind the enableCache to prepare for the next PRs.
* Fix infinite loop if unmemoized val passed to uDV
The current implementation of useDeferredValue will spawn a new
render any time the input value is different from the previous one. So
if you pass an unmemoized value (like an inline object), it will never
stop spawning new renders.
The fix is to only defer during an urgent render. If we're already
inside a transition, retry, offscreen, or other non-urgen render, then
we can use the latest value.
* Temporarily disable "long nested update" warning
DevTools' timeline profiler warns if an update inside a layout effect
results in an expensive re-render. However, it misattributes renders
that are spawned from a sync render at lower priority. This affects the
new implementation of useDeferredValue but it would also apply to things
like Offscreen.
It's not obvious to me how to fix this given how DevTools models the
idea of a "nested update" so I'm disabling the warning for now to
unblock the bugfix for useDeferredValue.
* Add fixture for comparing baseline render perf for renderToString and renderToPipeableStream
Modified from ssr2 and https://github.com/SuperOleg39/react-ssr-perf-test
* Implement buffering in pipeable streams
The previous implementation of pipeable streaming (Node) suffered some performance issues brought about by the high chunk counts and innefficiencies with how node streams handle this situation. In particular the use of cork/uncork was meant to alleviate this but these methods do not do anything unless the receiving Writable Stream implements _writev which many won't.
This change adopts the view based buffering techniques previously implemented for the Browser execution context. The main difference is the use of backpressure provided by the writable stream which is not implementable in the other context. Another change to note is the use of standards constructs like TextEncoder and TypedArrays.
* Implement encodeInto during flushCompletedQueues
encodeInto allows us to write directly to the view buffer that will end up getting streamed instead of encoding into an intermediate buffer and then copying that data.
Added a transitions stack for to keep track of which transitions are still happening for the current boundary.
* On the root, we will get all transitions that have been initiated for the corresponding lanes.
* Whenever we encounter a suspended boundary, we will add all transitions on the stack onto the boundary
* Whenever we encounter a boundary that just unsuspended, we will add all transitions on the boundary onto the stack
A transition will be considered complete when there are no boundaries that have the associated transition
Add pendingPassiveTransitions work loop module level variable. Because workInProgressTransitions might change before we process it in the passive effects, we introduce a new variable, pendingPassiveTransitions, where we store the transitions until we can actually process them in the commit phase.
We changed the implementation of root.transitionLanes so that, if there is no transitions for a given lane, we use null instead of an array. This means that this error is no longer valid, so we are removing it
When a tree goes offscreen, we unmount all the effects just like we
would in a normal deletion. (Conceptually it _is_ a deletion; we keep
the fiber around so we can reuse its state if the tree mounts again.)
If an offscreen component gets deleted "for real", we shouldn't unmount
it again.
The fix is to track on the stack whether we're inside a hidden tree.
We already had a stack variable for this purpose, called
`offscreenSubtreeWasHidden`, in another part of the commit phase, so I
reused that variable instead of creating a new one. (The name is a bit
confusing: "was" refers to the current tree before this commit. So, the
"previous current".)
Co-authored-by: dan <dan.abramov@me.com>
Similar to the previous step, this converts the deletion phase into
a single recursive function. Although there's less code, this one is
a bit trickier because it's already contains some stack-like logic
for tracking the nearest host parent. But instead of using the actual
stack, it repeatedly searches up the fiber return path to find the
nearest host parent.
Instead, I've changed it to track the nearest host parent on the
JS stack.
(We still search up the return path once, to set the initial host parent
right before entering a deleted tree. As a follow up, we can instead
push this to the stack as we traverse during the main mutation phase.)
Most of the commit phase uses iterative loops to traverse the tree.
Originally we thought this would be faster than using recursion, but
a while back @trueadm did some performance testing and found that the
loop was slower because we assign to the `return` pointer before
entering a subtree (which we have to do because the `return` pointer
is not always consistent; it could point to one of two fibers).
The other motivation is so we can take advantage of the JS stack to
track contextual information, like the nearest host parent.
We already use recursion in a few places; this changes the mutation
phase to use it, too.
This moves the try-catch from around each fiber's mutation phase to
direclty around each user function (effect function, callback, etc).
We already do this when unmounting because if one unmount function
errors, we still need to call all the others so they can clean up
their resources.
Previously we didn't bother to do this for anything but unmount,
because if a mount effect throws, we're going to delete that whole
tree anyway.
But now that we're switching from an iterative loop to a recursive one,
we don't want every call frame on the stack to have a try-catch, since
the error handling requires additional memory.
Wrapping every user function is a bit tedious, but it's better
for performance. Many of them already had try blocks around
them already.
We should always refine the type of fiber before checking the effect
flag, because the fiber tag is more specific.
Now we have a single switch statement for all mutation effects.
The fiber tag is more specific than the effect flag, so we should always
refine the type of work first, to minimize redundant checks.
In the next step I'll move all other other flag checks in this function
into the same switch statement.
There's not really any reason these should be separate functions. The
factoring has gotten sloppy and redundant because there's similar logic
in both places, which is more obvious now that they're combined.
Next I'll start combining the redundant branches.
commitWork is forked into a separate implementation for mutation mode
(DOM) and persistent mode (React Native). But unlike when it was first
introduced, there's more overlap than differences between the forks,
mainly because we've added new types of fibers. So this joins the two
forks and adds more local branches where the behavior actually
diverges: host nodes, host containers, and portals.
I'm about to refactor part of the commit phase to use recursion instead
of iteration. As part of that change, we will no longer assign the
`return` pointer when traversing into a subtree. So I'm disabling
the internal warning that fires if the return pointer is not consistent
with the parent during the commit phase.
I had originally added this warning to help prevent mistakes when
traversing the tree iteratively, but since we're intentionally switching
to recursion instead, we don't need it.