This was really meant to be there from the beginning. A `cache()`:ed
entry has a life time. On the server this ends when the render finishes.
On the client this ends when the cache of that scope gets refreshed.
When a cache is no longer needed, it should be possible to abort any
outstanding network requests or other resources. That's what
`cacheSignal()` gives you. It returns an `AbortSignal` which aborts when
the cache lifetime is done based on the same execution scope as a
`cache()`ed function - i.e. `AsyncLocalStorage` on the server or the
render scope on the client.
```js
import {cacheSignal} from 'react';
async function Component() {
await fetch(url, { signal: cacheSignal() });
}
```
For `fetch` in particular, a patch should really just do this
automatically for you. But it's useful for other resources like database
connections.
Another reason it's useful to have a `cacheSignal()` is to ignore any
errors that might have triggered from the act of being aborted. This is
just a general useful JavaScript pattern if you have access to a signal:
```js
async function getData(id, signal) {
try {
await queryDatabase(id, { signal });
} catch (x) {
if (!signal.aborted) {
logError(x); // only log if it's a real error and not due to cancellation
}
return null;
}
}
```
This just gets you a convenient way to get to it without drilling
through so a more idiomatic code in React might look something like.
```js
import {cacheSignal} from "react";
async function getData(id) {
try {
await queryDatabase(id);
} catch (x) {
if (!cacheSignal()?.aborted) {
logError(x);
}
return null;
}
}
```
If it's called outside of a React render, we normally treat any cached
functions as uncached. They're not an error call. They can still load
data. It's just not cached. This is not like an aborted signal because
then you couldn't issue any requests. It's also not like an infinite
abort signal because it's not actually cached forever. Therefore,
`cacheSignal()` returns `null` when called outside of a React render
scope.
Notably the `signal` option passed to `renderToReadableStream` in both
SSR (Fizz) and RSC (Flight Server) is not the same instance that comes
out of `cacheSignal()`. If you abort the `signal` passed in, then the
`cacheSignal()` is also aborted with the same reason. However, the
`cacheSignal()` can also get aborted if the render completes
successfully or fatally errors during render - allowing any outstanding
work that wasn't used to clean up. In the future we might also expand on
this to give different
[`TaskSignal`](https://developer.mozilla.org/en-US/docs/Web/API/TaskSignal)
to different scopes to pass different render or network priorities.
On the client version of `"react"` this exposes a noop (both for
Fiber/Fizz) due to `disableClientCache` flag but it's exposed so that
you can write shared code.
Includes #31412.
The issue is that `pushTreeFork` stores some global state when reconcile
children. This gets popped by `popTreeContext` in `completeWork`.
Normally `completeWork` returns its own `Fiber` again if it wants to do
a second pass which will call `pushTreeFork` again in the next pass.
However, `SuspenseList` doesn't return itself, it returns the next child
to work on.
The fix is to keep track of the count and push it again it when we
return the next child to attempt.
There are still some outstanding issues with hydration. Like the
backwards test still has the wrong behavior in it because it hydrates
backwards and so it picks up the DOM nodes in reverse order.
`tail="hidden"` also doesn't work correctly.
There's also another issue with `useId` and `AsyncIterable` in
SuspenseList when there's an unknown number of children. We don't
support those showing one at a time yet though so it's not an issue yet.
To fix it we need to add variable total count to the `useId` algorithm.
E.g. by falling back to varint encoding.
---------
Co-authored-by: Rick Hanlon <rickhanlonii@fb.com>
Co-authored-by: Ricky <rickhanlonii@gmail.com>
We want to change the defaults for `revealOrder` and `tail` on
SuspenseList. This is an intermediate step to allow experimental users
to upgrade.
To explicitly specify these options I added `revealOrder="independent"`
and `tail="visible"`.
I then added warnings if `undefined` or `null` is passed. You must now
always explicitly specify them. However, semantics are still preserved
for now until the next step.
We also want to change the rendering order of the `children` prop for
`revealOrder="backwards"`. As an intermediate step I first added
`revealOrder="unstable_legacy-backwards"` option. This will only be
temporary until all users can switch to the new `"backwards"` semantics
once we flip it in the next step.
I also clarified the types that the directional props requires iterable
children but not iterable inside of those. Rows with multiple items can
be modeled as explicit fragments.
Alternative to #33421. The difference is that this also adds an
underscore between the "R" and the ID.
The reason we wanted to use special characters is because we use the
full spectrum of A-Z 0-9 in our ID generation so we can basically
collide with any common word (or anyone using a similar algorithm,
base64 or even base16). It's a little less likely that someone would put
`_R_` specifically unless you generate like two IDs separated by
underscore.

## Summary
This PR fixes a likely incorrect condition in the
`scheduleUpdateOnFiber` function inside `ReactFiberWorkLoop.js`.
Previously, the code checked:
```js
(executionContext & RenderContext) !== NoLanes
````
However, `NoLanes` is part of the lane priority system, not the
execution context flags. The intent here seems to be to detect whether
the current execution context includes `RenderContext`, which should be
compared against `NoContext`, not `NoLanes`.
This fix replaces `NoLanes` with `NoContext` for semantic correctness
and consistency with other checks throughout the codebase.
**Fixes
[[#33169](https://github.com/facebook/react/issues/33169)](https://github.com/facebook/react/issues/33169)**
---
## How did you test this change?
I ran the following commands to validate correctness and ensure nothing
was broken:
* `yarn lint`
* `yarn linc`
* `yarn test`
* `yarn test --prod`
* `yarn flow`
* `yarn prettier`
All checks passed. Since this is a minor internal logic fix and doesn't
change public behavior or APIs, no additional tests are necessary at
this time.
## Summary
I am writing code that isn't so good, so I saw this error message many
times. It appears to have a typo. This PR fixes the typo.
## How did you test this change?
Ran the tests
When a new child of a fragment instance is inserted, we need to notify
the instance to keep any relevant tracking up to date. For example, we
automatically observe the new child with any active
IntersectionObserver.
For mutable renderers (DOM), we reuse the existing traversal in
`commitPlacement` that does the insertions for HostComponents. Immutable
renderers (Fabric) exit this path before the traversal though, so
currently we can't notify the fragment instances.
Here I've created a separate traversal in `commitPlacement`,
specifically for immutable renders when `enableFragmentRefs` is on.
We have many cases internally where the `containerInstance` resolves to
a comment node. `restoreRootViewTransitionName` is called when
`enableViewTransition` is on, even without introducing a
`<ViewTransition />`. So that means it can crash pages because
`containerInstance.style` is `undefined` just by turning on the flag.
This skips cancel/restore of root view transition name if a comment node is the root.
We support AsyncIterable (more so when it's a cached form like in coming
from Flight) as children.
This fixes some warnings and bugs when passed to SuspenseList.
Ideally SuspenseList with `tail="hidden"` should support unblocking
before the full result has resolved but that's an optimization on top.
We also might want to change semantics for this for
`revealOrder="backwards"` so it becomes possible to stream items in
reverse order.
This is a partial revert of #33094. It's true that we don't need the
server and client ViewTransition names to line up. However the server
does need to be able to generate deterministic names for itself. The
cheapest way to do that is using the useId algorithm. When it's used by
the server, the client needs to also materialize an ID even if it
doesn't use it.
And that doesn't disable with `update="none"`.
The principle here is that we want the content of a Portal to animate if
other things are animating with it but if other things aren't animating
then we don't.
Stacked on #33160, #33162, #33186 and #33188.
We have a special case that's awkward for default indicators. When you
start a new async Transition from `React.startTransition` then there's
not yet any associated root with the Transition because you haven't
necessarily `setState` on anything yet until the promise resolves.
That's what `entangleAsyncAction` handles by creating a lane that
everything entangles with until all async actions are done.
If there are no sync updates before the end of the event, we should
trigger a default indicator until either the async action completes
without update or if it gets entangled with some roots we should keep it
going until those roots are done.
Stacked on #33160.
The purpose of this is to avoid calling `onDefaultTransitionIndicator`
when a Default priority update acts as the loading indicator, but still
call it when unrelated Default updates happens nearby.
When we schedule Default priority work that gets batched with other
events in the same frame more or less. This helps optimize by doing less
work. However, that batching means that we can't separate work from one
setState from another. If we would consider all Default priority work in
a frame when determining whether to show the default we might never show
it in cases like when you have a recurring timer updating something.
This instead flushes the Default priority work eagerly along with the
sync work at the end of the event, if this event scheduled any
Transition work. This is then used to determine if the default indicator
needs to be shown.
Stacked on #33159.
This implements `onDefaultTransitionIndicator`.
The sequence is:
1) In `markRootUpdated` we schedule Transition updates as needing
`indicatorLanes` on the root. This tracks the lanes that currently need
an indicator to either start or remain going until this lane commits.
2) Track mutations during any commit. We use the same hook that view
transitions use here but instead of tracking it just per view transition
scope, we also track a global boolean for the whole root.
3) If a sync/default commit had any mutations, then we clear the
indicator lane for the `currentEventTransitionLane`. This requires that
the lane is still active while we do these commits. See #33159. In other
words, a sync update gets associated with the current transition and it
is assumed to be rendering the loading state for that corresponding
transition so we don't need a default indicator for this lane.
4) At the end of `processRootScheduleInMicrotask`, right before we're
about to enter a new "event transition lane" scope, it is no longer
possible to render any more loading states for the current transition
lane. That's when we invoke `onDefaultTransitionIndicator` for any roots
that have new indicator lanes.
5) When we commit, we remove the finished lanes from `indicatorLanes`
and once that reaches zero again, then we can clean up the default
indicator. This approach means that you can start multiple different
transitions while an indicator is still going but it won't stop/restart
each time. Instead, it'll wait until all are done before stopping.
Follow ups:
- [x] Default updates are currently not enough to cancel because those
aren't flush in the same microtask. That's unfortunate. #33186
- [x] Handle async actions before the setState. Since these don't
necessarily have a root this is tricky. #33190
- [x] Disable for `useDeferredValue`. ~Since it also goes through
`markRootUpdated` and schedules a Transition lane it'll get a default
indicator even though it probably shouldn't have one.~ EDIT: Turns out
this just works because it doesn't go through `markRootUpdated` when
work is left behind.
- [x] Implement built-in DOM version by default. #33162
When we're entangled with an async action lane we use that lane instead
of the currentEventTransitionLane. Conversely, if we start a new async
action lane we reuse the currentEventTransitionLane.
So they're basically supposed to be in sync but they're not if you
resolve the async action and then schedule new stuff in the same event.
Then you end up with two transitions in the same event with different
lanes.
By stashing it like this we fix that but it also gives us an opportunity
to check just the currentEventTransitionLane to see if this event
scheduled any regular Transition updates or Async Transitions.
This keeps track of the transition lane allocated for this event. I want
to be able to use the current one within sync work flushing to know
which lane needs its loading indicator cleared.
It's also a bit weird that transition work scheduled inside sync updates
in the same event aren't entangled with other transitions in that event
when `flushSync` is.
Therefore this moves it to reset after flushing.
It should have no impact. Just splitting it out into a separate PR for
an abundance of caution.
The only thing this might affect would be if the React internals throws
and it doesn't reset after. But really it doesn't really have to reset
and they're all entangled anyway.
Stacked on #33150.
We use `noop` functions in a lot of places as place holders. I don't
think there's any real optimizations we get from having separate
instances. This moves them to use a common instance in `shared/noop`.
This enables `focus` and `focusLast` methods on FragmentInstances to
search nested host components, depth first. Attempts focus on each child
and bails if one is successful. Previously, only the first level of host
children would attempt focus.
Now if we have an example like
```
component MenuItem() {
return (<div><a>{...}</a></div>)
}
component Menu() {
return <Fragment>{items.map(i => <MenuItem i={i} />)}</Fragment>
}
```
We can target focus on the first or last a tag, rather than checking
each wrapping div and then noop.
This adds `compareDocumentPosition(otherNode)` to fragment instances.
The semantics implemented are meant to match typical element
positioning, with some fragment specifics. See the unit tests for all
expectations.
- An element preceding a fragment is `Node.DOCUMENT_POSITION_PRECEDING`
- An element after a fragment is `Node.DOCUMENT_POSITION_FOLLOWING`
- An element containing the fragment is
`Node.DOCUMENT_POSITION_PRECEDING` and
`Node.DOCUMENT_POSITION_CONTAINING`
- An element within the fragment is
`Node.DOCUMENT_POSITION_CONTAINED_BY`
- An element compared against an empty fragment will result in
`Node.DOCUMENT_POSITION_DISCONNECTED` and
`Node.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC`
Since we assume a fragment instances target children are DOM siblings
and we want to compare the full fragment as a pseudo container, we can
compare against the first target child outside of handling the special
cases (empty fragments and contained elements).
Originally I thought it was important that SSR used the same View
Transition name as the client so that the Fizz runtime could emit those
names and then the client could pick up and take over. However, I no
longer believe that approach is feasible. Instead, the names can be
generated only during that particular animation.
Therefore we can simplify the auto name assignment to not have to
consider the hydration.
Stacked on #33129. Flagged behind `enableHydrationChangeEvent`.
If you type into a controlled input before hydration and something else
rerenders like a setState in an effect, then the controlled input will
reset to whatever React thought it was. Even with event replaying that
this is stacked on, if the second render happens before event replaying
has fired in a separate task.
We don't want to flush inside the commit phase because then things like
flushSync in these events wouldn't work since they're inside the commit
stack.
This flushes all event replaying between renders by flushing it at the
end of `flushSpawned` work. We've already committed at that point and is
about to either do subsequent renders or yield to event loop for passive
effects which could have these events fired anyway. This just ensures
that they've already happened by the time subsequent renders fire. This
means that there's now a type of event that fire between sync render
passes.
This fixes a long standing issue that controlled inputs gets out of sync
with the browser state if it's changed before we hydrate.
This resolves the issue by replaying the change events (click, input and
change) if the value has changed by the time we commit the hydration.
That way you can reflect the new value in state to bring it in sync. It
does this whether controlled or uncontrolled.
The idea is that this should be ok to replay because it's similar to the
continuous events in that it doesn't replay a sequence but only reflects
the current state of the tree.
Since this is a breaking change I added it behind
`enableHydrationChangeEvent` flag.
There is still an additional issue remaining that I intend to address in
a follow up. If a `useLayoutEffect` triggers an sync rerender on
hydration (always a bad idea) then that can rerender before we have had
a chance to replay the change events. If that renders through a input
then that input will always override the browser value with the
controlled value. Which will reset it before we've had a change to
update to the new value.
Adds Fragment Ref support to RN through the Fabric config, starting with
`observeUsing`/`unobserveUsing`. This is mostly a copy from the
implementation on DOM, and some of it can likely be shared in the future
but keeping it separate for now and we can refactor as we add more
features.
Added a basic test with Fabric, but testing specific methods requires so
much mocking that it doesn't seem valuable here.
I built Fabric and ran on the Catalyst app internally to test with
intersection observers end to end.
Stacked on #32736.
That way you can find the owner stack of each component that rerendered
for context.
In addition to the JSX callsite tasks that we already track, I also
added tracking of the first `setState` call before rendering.
We then run the "Update" entries in that task. That way you can find the
callsite of the first setState and therefore the "cause" of a render
starting by selecting the "Update" track.
Unfortunately this is blocked on bugs in Chrome that makes it so that
these stacks are not reliable in the Performance tab. It basically just
doesn't work.
This is a new extension that Chrome added to the existing
`console.timeStamp` similar to the extensions added to
`performance.measure`. This one should be significantly faster because
it doesn't have the extra object indirection, it doesn't return a
`PerformanceMeasure` entry and doesn't register itself with the global
system of entries.
I also use `performance.measure` in DEV for errors since we can attach
the error to the `properties` extension which doesn't exist for
`console.timeStamp`.
A downside of using this API is that there's no programmatic API for the
site itself to collect its own logs from React. Which the previous
allowed us to use the standard `performance.getEntries()` for. The
recommendation instead will be for the site to patch `console.timeStamp`
if it wants to collect measurements from React just like you're
recommended to patch `console.error` or `fetch` or whatever to collect
other instrumentation metrics.
This extension works in Chrome canary but it doesn't yet work fully in
Chrome stable. We might want to wait until it has propagated to Chrome
to stable. It should be in Chrome 136.
`requestFormReset` incorrectly tries to get the current dispatch queue
from the Fiber. However, the Fiber might be the workInProgress which is
an inconsistent state.
This hack just tries the other Fiber if it detects one of the known
inconsistent states but there can be more.
Really we should stash the dispatch queue somewhere stateful which is
effectively what `setState` does by binding it to the closure.
Stacked on #32862 and #32842.
This means that Activity boundaries now act as boundaries which can have
their effects mounted independently. Just like Suspense boundaries, we
hydrate the outer content first and then start hydrating the content in
an Offscreen lane. Flowing props or interacting with the content
increases the priority just like Suspense boundaries.
This skips emitting even the comments for `<Activity mode="hidden">` so
we don't hydrate those. Instead those are deferred to a later client
render.
The implementation are just forked copies of the SuspenseComponent
branches and then carefully going through each line and tweaking it.
The main interesting bit is that, unlike Suspense, Activity boundaries
don't have fallbacks so all those branches where you might commit a
suspended tree disappears. Instead, if something suspends while
hydration, we can just leave the dehydrated content in place. However,
if something does suspend during client rendering then it should bubble
up to the parent. Therefore, we have to be careful to only
pushSuspenseHandler when hydrating. That's really the main difference.
This just uses the existing basic Activity tests but I've started work
on port all of the applicable Suspense tests in SelectiveHydration-test
and PartialHydration-test to Activity versions.
Stacked on #32851 and #32900.
This implements the equivalent Configs for ActivityInstance as we have
for SuspenseInstance. These can be implemented as comments but they
don't have to be and can be implemented differently in the renderer.
This seems like a lot duplication but it's actually ends mostly just
calling the same methods underneath and the wrappers compiles out.
This doesn't leave the Activity dehydrated yet. It just hydrates into it
immediately.
I think this was probably just copy-paste from the Suspense path.
It shouldn't matter what the previous state of an Offscreen boundary
was. What matters is that it's now hidden and therefore if it suspends,
we can just leave it as is without the tree becoming inconsistent.
Found this bug while working on Activity. There's a weird edge case when
a dehydrated Suspense boundary is a direct child of another Suspense
boundary which is hydrated but then it resuspends without forcing the
inner one to hydrate/delete.
It used to just leave that in place because hiding/unhiding didn't deal
with dehydrated fragments.
Not sure this is really worth fixing.
I found a bug even before the Activity hydration stuff.
If we're hydrating an Offscreen boundary in its "hidden" state it won't
have any content to hydrate so will trigger hydration errors (which are
then eaten by the Offscreen boundary itself). Leaving it not prewarmed.
This doesn't happen in the simple case because we'd be hydrating at a
higher priority than Offscreen at the root, and those are deferred to
Offscreen by not having higher priority. However, we've hydrating at the
Offscreen priority, which we do inside Suspense boundaries, then it
tries to hydrate against an empty set.
I ended up moving this to the Activity boundary in a future PR since
it's the SSR side that decided where to not render something and it only
has a concept of Activity, no Offscreen.
https://github.com/facebook/react/pull/32863/commits/1dc05a5e2222e18fc3a2062ee1bd957109e21344#diff-d5166797ebbc5b646a49e6a06a049330ca617985d7a6edf3ad1641b43fde1ddfR1111
Since `hidden` is a prop on arbitrary DOM elements it's a common mistake
to think that it would also work that way on `<Activity>` but it
doesn't. In fact, we even had this mistakes in our own tests.
Maybe there's an argument that we should actually just support it but we
also have more modes planned.
So this adds a warning. It should also already be covered by TypeScript.
It used to be that in `__DEV__` we wrapped this `renderWithHooks`,
`checkDidRenderIdHook` pair in calls to `setIsRendering()`. However,
that dev-only bookkeeping was removed in
https://github.com/facebook/react/pull/29206 leaving this redundant
check which runs identical code in dev and in prod.
## Test Plan
* Manually confirm both cases are the same
* GitHub CI tests
We use the Update flag to track if a View Transition had any mutations
or relayout. Unlike the other usage of it, this is just temporary state
during the commit phase.
Normally the flags gets used in the render phase and we reset it when we
rerender but in the case of "nested" updates, those trees didn't update.
We're only looking for relayouts. So we need to manually reset it before
we start using it.
We probably shouldn't abuse the Update flag for this and instead use
something like temporary state on ViewTransitionState.
Stacked on #32838.
We don't always type the Props of built-ins. This adds typing for most
of the built-ins.
When we did type them, we used to put it in the `ReactFiber...Component`
files but any public API like this can be implemented in other renderers
too such as Fizz. So I moved them to `shared/ReactTypes` which is where
we put other public API types (that are not already built-in to Flow).
That way Fizz can import them and assert properly when it accesses the
props.
Even if the `enableSuspenseyImages` flag is off.
Started View Transitions already wait for Suspensey Fonts and this is
another Suspensey feature that is even more important for View
Transitions - even though we eventually want it all the time. So this
uses `<ViewTransition>` as an early opt-in for that tree into Suspensey
Images, which we can ship in a minor.
If you're doing an update inside a ViewTransition then we're eligible to
start a ViewTransition in any Transition that might suspend. Even if
that doesn't end up animating after all, we still consider it Suspensey.
We could try to suspend inside the startViewTransition but that's not
how it would work with `enableSuspenseyImages` on and we can't do that
for startGestureTransition.
Even so we still need some opt-in to trigger the Suspense fallback even
before we know whether we'll animate or not. So the simple solution is
just that `<ViewTransition>` opts in the whole subtree into Suspensey
Images in general.
In this PR I disable `enableSuspenseyImages` in experimental so that we
can instead test the path that only enables it inside `<ViewTransition>`
tree since that's the path that would next graduate to a minor.
Behind the `enableSrcObject` flag. This is revisiting a variant of what
was discussed in #11163.
Instead of supporting the [`srcObject`
property](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/srcObject)
as a separate name, this adds an overload of `src` to allow objects to
be passed. The DOM needs to add separate properties for the object forms
since you read back but it doesn't make sense for React's write-only API
to do that. Similar to how we'll like add an overload for
`popoverTarget` instead of calling it `popoverTargetElement` and how
`style` accepts an object and it's not `styleObject={{...}}`.
There are a number of reason to revisit this.
- It's just way more convenient to have this built-in and it makes
conceptual sense. We typically support declarative APIs and polyfill
them when necessary.
- RSC supports Blobs and by having it built-in you don't need a Client
Component wrapper to render it where as doing it with effects would
require more complex wrappers. By picking Blobs over base64,
client-navigations can use the more optimized binary encoding in the RSC
protocol.
- The timing aspect of coordinating it with Suspensey images and image
decoding is a bit tricky to get right because if you set it in an effect
it's too late because you've already rendered it.
- SSR gets complicated when done in user space because you have to
handle both branches. Likely with `useSyncExternalStore`.
- By having it built-in we could optimize the payloads shared between
RSC payloads embedded in the HTML and data URLs.
This does not support objects for `<source src>` nor `<img srcset>`.
Those don't really have equivalents in the DOM neither. They're mainly
for picking an option when you don't know programmatically. However, for
this use case you're really better off picking a variant before
generating the blobs.
We may support Response objects in the future too as per
https://github.com/whatwg/fetch/issues/49
When `startTime` still has its initial value of `-1.1` we must not call
`logComponentMount`. This can occur when rendering a `'next/dynamic'`
component with `{ssr: false}` in a client component, for example.
Unfortunately, I didn't manage to reproduce this scenario in a unit
test.
Safari has a bug where if you put a block element inside an inline
element and the inline element has a `view-transition-name` assigned it
finds it as duplicate names.
https://bugs.webkit.org/show_bug.cgi?id=290923
This adds a warning if we detect this scenario in dev mode.
For the case where it renders into a single block, we can model this by
making the parent either `block` or `inline-block` automatically to fix
the issue. So we do that to automatically cover simple cases like
`<a><div>...</div></a>`. This unfortunately causes layout/styling thrash
so we might want to delete it once the bug has been fixed in enough
Safari versions.
Found a bug that occurs during a specific combination of very subtle
implementation details.
It occurs sometimes (not always) when 1) a transition is scheduled
during a popstate event, and 2) as a result, a new value is passed to an
already-mounted useDeferredValue hook.
The fix is relatively straightforward, and I found it almost
immediately; it took a bit longer to figure out exactly how the scenario
occurred in production and create a test case to simulate it.
Rather than couple the test to the implementation details, I've chosen
to keep it as high-level as possible so that it doesn't break if the
details change. In the future, it might not be trigger the exact set of
internal circumstances anymore, but it could be useful for catching
similar bugs because it represents a realistic real world situation —
namely, switching tabs repeatedly in an app that uses useDeferredValue.