* 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.
This PR moves the code for transition tracing in the mutation phase that adds transitions to the pending callbacks object (to be called sometime later after paint) from the mutation to the passive phase.
Things to think about:
Passive effects can be flushed before or after paint. How do we make sure that we get the correct end time for the interaction?
* Switched RulesOfHooks.js to use BigInt. Added test and updated .eslintrc.js to use es2020.
* Added BigInt as readonly global in eslintrc.cjs.js and eslintrc.cjs2015.js
* Added comment to RulesOfHooks.js that gets rid of BigInt eslint error
* Got rid of changes in .eslintrc.js and yarn.lock
* Move global down
Co-authored-by: stephen cyron <stephen.cyron@fdmgroup.com>
Co-authored-by: dan <dan.abramov@gmail.com>
This type isn't exported so it's technically not public.
This object mimics a ReadableStream.
Currently this is safe to destructure and call separately but I'm not sure
that's even guaranteed. It should probably be treated as a class in docs.