And `useDeferredValue`.
The options were already disabled in previous commits, so this doesn't
change any behavior. I upated type signatures and cleaned up the hook
implementation a bit — no longer have to wrap the `start` method with
`useCallback`, because its only remaining dependency is a `setState`
method, which never changes. Instead, we can store the `start` method
on a ref.
Now that the options in SuspenseConfig are no longer supported, the
only thing we use it for is to track whether an update is part of
a transition.
I've renamed `ReactCurrentBatchConfig.suspense` to
`ReactCurrentBatchConfig.transition`, and changed the type to a number.
The number is always either 0 or 1. I could have made it a boolean;
however, most likely this will eventually be either a Lane or an
incrementing identifier.
The `withSuspenseConfig` export still exists until we've removed
all the callers from www.
* Disable busyDelayMs and busyMinDurationMs
Refer to explanation in previous commit.
* Remove unnecessary work loop variables
Since we no longer support SuspenseConfig options, we don't need to
track these values.
* Remove unnecessary Update fields
* Remove distinction between long, short transitions
We're removing the `timeoutMs` option, so there's no longer any
distinction between "short" and "long" transitions. They're all treated
the same.
This commit doesn't remove `timeoutMs` yet, only combines the internal
priority levels.
* Disable `timeoutMs` argument
tl;dr
-----
- We're removing the `timeoutMs` argument from `useTransition`.
- Transitions will either immediately switch to a skeleton/placeholder
view (when loading new content) or wait indefinitely until the data
resolves (when refreshing stale content).
- This commit disables the `timeoutMS` so that the API has the desired
semantics. It doesn't yet update the types or migrate all the test
callers. I'll do those steps in follow-up PRs.
Motivation
----------
Currently, transitions initiated by `startTransition` / `useTransition`
accept a `timeoutMs` option. You can use this to control the maximum
amount of time that a transition is allowed to delay before we give up
and show a placeholder.
What we've discovered is that, in practice, every transition falls into
one of two categories: a **load** or a **refresh**:
- **Loading a new screen**: show the next screen as soon as possible,
even if the data hasn't finished loading. Use a skeleton/placeholder
UI to show progress.
- **Refreshing a screen that's already visible**: keep showing the
current screen indefinitely, for as long as it takes to load the fresh
data, even if the current data is stale. Use a pending state (and
maybe a busy indicator) to show progress.
In other words, transitions should either *delay indefinitely* (for a
refresh) or they should show a placeholder *instantly* (for a load).
There's not much use for transitions that are delayed for a
small-but-noticeable amount of time.
So, the plan is to remove the `timeoutMs` option. Instead, we'll assign
an effective timeout of `0` for loads, and `Infinity` for refreshes.
The mechanism for distinguishing a load from a refresh already exists in
the current model. If a component suspends, and the nearest Suspense
boundary hasn't already mounted, we treat that as a load, because
there's nothing on the screen. However, if the nearest boundary is
mounted, we treat that as a refresh, since it's already showing content.
If you need to fix a transition to be treated as a load instead of a
refresh, or vice versa, the solution will involve rearranging the
location of your Suspense boundaries. It may also involve adding a key.
We're still working on proper documentation for these patterns. In the
meantime, please reach out to us if you run into problems that you're
unsure how to fix.
We will remove `timeoutMs` from `useDeferredValue`, too, and apply the
same load versus refresh semantics to the update that spawns the
deferred value.
Note that there are other types of delays that are not related to
transitions; for example, we will still throttle the appearance of
nested placeholders (we refer to this as the placeholder "train model"),
and we may still apply a Just Noticeable Difference heuristic (JND) in
some cases. These aren't going anywhere. (Well, the JND heuristic might
but for different reasons than those discussed above.)
* ensure getDisplayName is only called on functions
* add SuspenseList to Dev tools element names
* Add SuspenseList and pass tests
* Import SuspenseList directly
* run prettier
* Refactor tests to use real components
* run linter
* Lint rule to forbid access of cross-fork fields
We use a shared Fiber type for both reconciler forks (old and new). It
is a superset of all the fields used by both forks. However, there are
some fields that should only be used in the new fork, and others that
should only be used in the old fork.
Ideally we would enforce this with separate Flow types for each fork.
The problem is that the Fiber type is accessed by some packages outside
the reconciler (like React DOM), and get passed into the reconciler as
arguments. So there's no way to fork the Fiber type without also forking
the packages where they are used. FiberRoot has the same issue.
Instead, I've added a lint rule that forbids cross-fork access of
fork-specific fields. Fields that end in `_old` or `_new` are forbidden
from being used inside the new or old fork respectively. Or you can
specific custom fields using the ESLint plugin options.
I used this plugin to find and remove references to the effect list
in d2e914a.
* Mark effect list fields as old
And `subtreeTag` as new.
I didn't mark `lastEffect` because that name is also used by the
Hook type. Not super important; could rename to `lastEffect_old` but
idk if it's worth the effort.
* Failing test for #19608
* Attach Listeners Eagerly to Roots and Portal Containers
* Forbid createEventHandle with custom events
We can't support this without adding more complexity. It's not clear that this is even desirable, as none of our existing use cases need custom events. This API primarily exists as a deprecation strategy for Flare, so I don't think it is important to expand its support beyond what Flare replacement code currently needs. We can later revisit it with a better understanding of the eager/lazy tradeoff but for now let's remove the inconsistency.
* Reduce risk by changing condition only under the flag
Co-authored-by: koba04 <koba0004@gmail.com>
* Remove `firstEffect` null check
This is the last remaining place where the effect list has semantic
implications.
I've replaced it with a check of `effectTag` and `subtreeTag`, to see
if there are any effects in the whole tree. This matches the semantics
of the old check. However, I think only reason this optimization exists
is because it affects profiling. We should reconsider whether this
is necessary.
* Remove remaining references to effect list
We no longer use the effect list anywhere in our implementation. It's
been replaced by a recursive traversal in the commit phase.
This removes all references to the effect list in the new fork.
* Add html_all_collection type to correct typeof document.all
* process HTMLAllCollection like HTMLElement + fix flow issue
* fix lint
* move flow fix comment
* Make it work with iframes too
* optimize how we get html_all_collection type
* use once Object.prototype.toString.call
* Use Retry lane for resuming CPU suspended work
* Use a global render timeout for CPU suspense heuristics
* Fix profiler test since we're now reading time more often
* Sync to new reconciler
* Test synchronously rerendering should not render more rows
* Add flow to SyntheticEvent
* Minimal implementation of known and unknown synthetic events
* less casting
* Update EnterLeaveEventPlugin.js
Co-authored-by: Dan Abramov <dan.abramov@gmail.com>
We use safelyCallDestroy for commitUnmount and passive effects unmounts but we call destroy directly in commitHookEffectListUnmount (AKA layout effects unmounts because we don't use this anywhere else). This PR changes the direct destroy call to safelyCallDestroy for consistency
The behavior of error boundaries for passive effects that throw during cleanup was recently changed so that React ignores boundaries which are also unmounting in favor of still-mounted boundaries. This commit implements that same behavior for layout effects (useLayoutEffect, componentWillUnmount, and ref-detachment).
The new, skip-unmounting-boundaries behavior is behind a feature flag (`skipUnmountedBoundaries`).
A passive effect's cleanup function may throw after an unmount. Prior to this commit, such an error would be ignored. (React would not notify any error boundaries.) After this commit, React's behavior varies depending on which reconciler fork is being used.
For the old reconciler, React will call componentDidCatch for the nearest unmounted error boundary (if there is one). If there are no unmounted error boundaries, React will still swallow the error because the return pointer has been disconnected, so the normal error handling logic does not know how to traverse the tree to find the nearest still-mounted ancestor.
For the new reconciler, React will skip any unmounted boundaries and look for a still-mounted boundary. If one is found, it will call getDerivedStateFromError and/or componentDidCatch (depending on the type of boundary).
Tests have been added for both reconciler variants for now.
These stacks improve the profiler data but they're expensive to generate and generating them can also cause runtime errors in larger applications (although an exact repro has been hard to nail down). Removing them for now. We can revisit adding them after this profiler has been integrated into the DevTools extension and we can generate them lazily.
No longer need this, since we have starvation protection in userspace.
This will also allow us to remove the concept from the Scheduler
package, which is nice because `postTask` doesn't currently support it.
`shouldYield` will currently return `true` if there's a higher priority
task in the Scheduler queue.
Since we yield every 5ms anyway, this doesn't really have any practical
benefit. On the contrary, the extra checks on every `shouldYield` call
are wasteful.
* [eslint-plugin-react-cooks] Report constant constructions
The dependency array passed to a React hook can be thought of as a list of cache keys. On each render, if any dependency is not `===` its previous value, the hook will be rerun. Constructing a new object/array/function/etc directly within your render function means that the value will be referentially unique on each render. If you then use that value as a hook dependency, that hook will get a "cache miss" on every render, making the dependency array useless.
This can be especially dangerous since it can cascade. If a hook such as `useMemo` is rerun on each render, not only are we bypassing the option to avoid potentially expensive work, but the value _returned_ by `useMemo` may end up being referentially unique on each render causing other downstream hooks or memoized components to become deoptimized.
* Fix/remove existing tests
* Don't give an autofix of wrapping object declarations
It may not be safe to just wrap the declaration of an object, since the object may get mutated.
Only offer this autofix for functions which are unlikely to get mutated.
Also, update the message to clarify that the entire construction of the value should get wrapped.
* Handle the long tail of nodes that will be referentially unique
* Catch let/var constant constructions on initial assignment
* Trim trailing whitespace
* Address feedback from @gaearon
* Rename "assignment" to "initialization"
* Add test for a constant construction used in multiple dependency arrays
Because `postTask` returns a promise, errors inside a `postTask`
callback result in the promise being rejected.
If we don't catch those errors, then the browser will report an
"Unhandled promise rejection" error. This is a confusing message to see
in the console, because the fact that `postTask` is a promise-based API
is an implementation detail from the perspective of the developer.
"Promise rejection" is a red herring.
On the other hand, if we do catch those errors, then we need to report
the error to the user in some other way.
What we really want is the default error reporting behavior that a
normal, non-Promise browser event gets.
So, we'll re-throw inside `setTimeout`.