This is similar to #28771 but for isomorphic. We need a make over for
these dispatchers anyway so this is the first step. Also helps flush out
some internals usage that will break anyway.
It flattens the inner mutable objects onto the ReactSharedInternals.
DiffTrain build for commit https://github.com/facebook/react/commit/d50323eb845c5fde0d720cae888bf35dedd05506.
This PR moves `flushSync` out of the reconciler. there is still an
internal implementation that is used when these semantics are needed for
React methods such as `unmount` on roots.
This new isomorphic `flushSync` is only used in builds that no longer
support legacy mode.
Additionally all the internal uses of flushSync in the reconciler have
been replaced with more direct methods. There is a new
`updateContainerSync` method which updates a container but forces it to
the Sync lane and flushes passive effects if necessary. This combined
with flushSyncWork can be used to replace flushSync for all instances of
internal usage.
We still maintain the original flushSync implementation as
`flushSyncFromReconciler` because it will be used as the flushSync
implementation for FB builds. This is because it has special legacy mode
handling that the new isomorphic implementation does not need to
consider. It will be removed from production OSS builds by closure
though
DiffTrain build for commit https://github.com/facebook/react/commit/4c12339ce3fa398050d1026c616ea43d43dcaf4a.
Currently updatePriority is tracked in the reconciler. `flushSync` is
going to be implemented reconciler agnostic soon and we need to move the
tracking of this state to the renderer and out of reconciler. This
change implements new renderer bin dings for getCurrentUpdatePriority
and setCurrentUpdatePriority.
I was originally going to have the getter also do the event priority
defaulting using window.event so we eliminate getCur rentEventPriority
but this makes all the callsites where we store the true current
updatePriority on the stack harder to work with so for now they remain
separate.
I also moved runWithPriority to the renderer since it really belongs
whereever the state is being managed and it is only currently exposed in
the DOM renderer.
Additionally the current update priority is not stored on
ReactDOMSharedInternals. While not particularly meaningful in this
change it opens the door to implementing `flushSync` outside of the
reconciler
DiffTrain build for commit https://github.com/facebook/react/commit/8e1462e8c471fbec98aac2b3e1326498d0ff7139.
Follow up to #28768.
The modern JSX runtime (`jsx`) does not need to check if each prop is a
direct property with `hasOwnProperty` because the compiler always passes
a plain object.
I'll leave the check in the old JSX runtime (`createElement`) since that
one can be called manually with any kind of object, and if there were
old user code that relied on this for some reason, it would be using
that runtime.
DiffTrain build for commit https://github.com/facebook/react/commit/0b3b8a6a354b90fe76a9d82bb34487e5d2f71203.
(Unless "key" is spread onto the element.)
Historically, the JSX runtime clones the props object that is passed in.
We've done this for two reasons.
One reason is that there are certain prop names that are reserved by
React, like `key` and (before React 19) `ref`. These are not actual
props and are not observable by the target component; React uses them
internally but removes them from the props object before passing them to
userspace.
The second reason is that the classic JSX runtime, `createElement`, is
both a compiler target _and_ a public API that can be called manually.
Therefore, we can't assume that the props object that is passed into
`createElement` won't be mutated by userspace code after it is passed
in.
However, the new JSX runtime, `jsx`, is not a public API — it's solely a
compiler target, and the compiler _will_ always pass a fresh, inline
object. So the only reason to clone the props is if a reserved prop name
is used.
In React 19, `ref` is no longer a reserved prop name, and `key` will
only appear in the props object if it is spread onto the element.
(Because if `key` is statically defined, the compiler will pass it as a
separate argument to the `jsx` function.) So the only remaining reason
to clone the props object is if `key` is spread onto the element, which
is a rare case, and also triggers a warning in development.
In a future release, we will not remove a spread key from the props
object. (But we'll still warn.) We'll always pass the object straight
through.
The expected impact is much faster JSX element creation, which in many
apps is a significant slice of the overall runtime cost of rendering.
DiffTrain build for commit https://github.com/facebook/react/commit/d1547defe34cee6326a61059148afc83228d8ecf.
`delete` causes an object (in V8, and maybe other engines) to deopt to a
dictionary instead of a class. Instead of `assign` + `delete`, manually
iterate over all the properties, like the JSX runtime does.
To avoid copying the object twice I moved the `ref` prop removal to come
before handling default props. If we already cloned the props to remove
`ref`, then we can skip cloning again to handle default props.
DiffTrain build for commit https://github.com/facebook/react/commit/bfd8da807c75a2d123627415f9eaf2d36ac3ed6a.
This implements the concept of a DEV-only "owner" for Server Components.
The owner concept isn't really super useful. We barely use it anymore,
but we do have it as a concept in DevTools in a couple of cases so this
adds it for parity. However, this is mainly interesting because it could
be used to wire up future owner-based stacks.
I do this by outlining the DebugInfo for a Server Component
(ReactComponentInfo). Then I just rely on Flight deduping to refer to
that. I refer to the same thing by referential equality so that we can
associate a Server Component parent in DebugInfo with an owner.
If you suspend and replay a Server Component, we have to restore the
same owner. To do that, I did a little ugly hack and stashed it on the
thenable state object. Felt unnecessarily complicated to add a stateful
wrapper for this one dev-only case.
The owner could really be anything since it could be coming from a
different implementation. Because this is the first time we have an
owner other than Fiber, I have to fix up a bunch of places that assumes
Fiber. I mainly did the `typeof owner.tag === 'number'` to assume it's a
Fiber for now.
This also doesn't actually add it to DevTools / RN Inspector yet. I just
ignore them there for now.
Because Server Components can be async the owner isn't tracked after an
await. We need per-component AsyncLocalStorage for that. This can be
done in a follow up.
DiffTrain build for commit https://github.com/facebook/react/commit/f33a6b69c6cb406ea0cc51d07bc4d3fd2d8d8744.
Based on:
- #28464
---
This moves the entire string ref implementation out Fiber and into the
JSX runtime. The string is converted to a callback ref during element
creation. This is a subtle change in behavior, because it will have
already been converted to a callback ref if you access element.prop.ref
or element.ref. But this is only for Meta, because string refs are
disabled entirely in open source. And if it leads to an issue in
practice, the solution is to switch to a different ref type, which Meta
is going to do regardless.
DiffTrain build for commit https://github.com/facebook/react/commit/e3ebcd54b98a4f8f5a9f7e63982fa75578b648ed.
This removes defaultProps support for all component types except for
classes. We've chosen to continue supporting defaultProps for classes
because lots of older code relies on it, and unlike function components,
(which can use default params), there's no straightforward alternative.
By implication, it also removes support for setting defaultProps on
`React.lazy` wrapper. So this will not work:
```js
const MyClassComponent = React.lazy(() => import('./MyClassComponent'));
// MyClassComponent is not actually a class; it's a lazy wrapper. So
// defaultProps does not work.
MyClassComponent.defaultProps = { foo: 'bar' };
```
However, if you set the default props on the class itself, then it's
fine.
For classes, this change also moves where defaultProps are resolved.
Previously, defaultProps were resolved by the JSX runtime. This change
is only observable if you introspect a JSX element, which is relatively
rare but does happen.
In other words, previously `<ClassWithDefaultProp />.props.aDefaultProp`
would resolve to the default prop value, but now it does not.
DiffTrain build for commit https://github.com/facebook/react/commit/48b4ecc9012638ed51b275aad24b2086b8215e32.
We basically have four kinds of recoverable errors:
- Hydration mismatches.
- Server errored but client didn't.
- Hydration render errored but client render didn't (in Root or Suspense
boundary).
- Concurrent render errored but synchronous render didn't.
For the first three we log an additional error that the root or Suspense
boundary didn't error. This provides some context about what happened.
However, the problem is that for hydration mismatches that's unnecessary
extra context that is confusing. We also don't log any additional
context for concurrent render errors that could recover. This used to be
the only recoverable error so it didn't need extra context but now we
need to distinguish them. When we log these to `reportError` it's
confusing to just see the error because you didn't see anything error on
the page. It's also hard to group them together as one.
In this PR, I remove the unnecessary context for hydration mismatches.
For hydration and concurrent errors, I now wrap them in an error that
describes that what happened but then use the new `cause` field to link
the original error so we can keep that as the cause. The error that
happened was that hydration client rendered or you deopted to sync
render, the cause of that error is some other error.
For server errors, we control the Error object so I already had added
some context to that error object's message. Since we hide the message
in prod, it's nice not to have the raw message in DEV neither. We could
potentially split these into two errors for parity though.
DiffTrain build for commit https://github.com/facebook/react/commit/6090cab099a8f7f373e04c7eb2937425a8f80f80.
Cleanup enableUseRefAccessWarning flag
I don't think this flag has a path forward in the current
implementation. The detection by stack trace is too brittle to detect
the lazy initialization pattern reliably (see e.g. some internal tests
that expect the warning because they use lazy intialization, but a
slightly different pattern then the expected pattern.
I think a new version of this could be to fully ban ref access during
render with an alternative API for the exceptional cases that today
require ref access during render.
DiffTrain build for commit https://github.com/facebook/react/commit/20e710aeab3e03809c82d134171986ea270026a0.
When a ref is passed to a class component, the class instance is
attached to the ref's current property automatically. This different
from function components, where you have to do something extra to attach
a ref to an instance, like passing the ref to `useImperativeHandle`.
Existing class component code is written with the assumption that a ref
will not be passed through as a prop. For example, class components that
act as indirections often spread `this.props` onto a child component. To
maintain this expectation, we should remove the ref from the props
object ("consume" it) before passing it to lifecycle methods. Without
this change, much existing code will break because the ref will attach
to the inner component instead of the outer one.
This is not an issue for function components because we used to warn if
you passed a ref to a function component. Instead, you had to use
`forwardRef`, which also implements this "consuming" behavior.
There are a few places in the reconciler where we modify the fiber's
internal props object before passing it to userspace. The trickiest one
is class components, because the props object gets exposed in many
different places, including as a property on the class instance.
This was already accounted for when we added support for setting default
props on a lazy wrapper (i.e. `React.lazy` that resolves to a class
component).
In all of these same places, we will also need to remove the ref prop
when `enableRefAsProp` is on.
Closes#28602
---------
Co-authored-by: Jan Kassens <jan@kassens.net>
DiffTrain build for commit https://github.com/facebook/react/commit/dc545c8d6eaca87c8d5cabfab6e1c768ecafe426.
This PR relands #28672 on top of the flag removal and the test
demonstrating a breakage in Suspense for legacy mode.
React has deprecated module pattern Function Components for many years
at this point. Supporting this pattern required React to have a concept
of an indeterminate component so that when a component first renders it
can turn into either a ClassComponent or a FunctionComponent depending
on what it returns. While this feature was deprecated and put behind a
flag it is still in stable. This change remvoes the flag, removes the
warnings, and removes the concept of IndeterminateComponent from the
React codebase.
While removing IndeterminateComponent type Seb and I discovered that we
needed a concept of IncompleteFunctionComponent to support Suspense in
legacy mode. This new work tag is only needed as long as legacy mode is
around and ideally any code that considers this tag will be excludable
from OSS builds once we land extra gates using `disableLegacyMode` flag.
DiffTrain build for commit https://github.com/facebook/react/commit/5998a775194f491afa5d3badd9afe9ceaf12845e.
Don't need to track it separately on the captured value anymore.
Shouldn't be in the types.
I used a getter for the warning instead because Proxies are kind of
heavy weight options for this kind of warning. We typically use getters.
DiffTrain build for commit https://github.com/facebook/react/commit/df95577db0d1d7ca383f281bc1d9e6ba5579bef2.
We previously only included the component stack.
Cleaned up the fields in Fizz server that wasn't using consistent hidden
classes in dev vs prod.
Added a prefix to errors serialized from server rendering. It can be a
bit confusing to see where this error came from otherwise since it
didn't come from elsewhere on the client. It's really kind of confusing
with other recoverable errors that happen on the client too.
DiffTrain build for commit https://github.com/facebook/react/commit/b9149cc6e6442389accf1f7c34a77ba2e6e52b5e.
When an error boundary catches an error during hydration it'll try to
render the error state which will then try to hydrate that state,
causing hydration warnings.
When an error happens inside a Suspense boundary during hydration, we
instead let the boundary catch it and restart a client render from
there. However, when it's in the root we instead let it fail the root
and do the sync recovery pass. This didn't consider that we might hit an
error boundary first so this just skips the error boundary in that case.
We should probably instead let the root do a concurrent client render in
this same pass instead to unify with Suspense boundaries.
DiffTrain build for commit https://github.com/facebook/react/commit/5d4b7587da52dd81bc5c366b909c4511e2970cd1.
## Summary
Makes a few changes to align React Native feature flags for open source
and internal test renderer configurations.
* Enable `enableSchedulingProfiler` for profiling builds.
* Align `ReactFeatureFlags.test-renderer.native.js` (with
`ReactFeatureFlags.native-fb.js`).
* Enable `enableUseMemoCacheHook`.
* Enable `enableFizzExternalRuntime`.
* Disable `alwaysThrottleRetries`.
## How did you test this change?
Ran the following successfully:
```
$ yarn test
$ yarn flow native
$ yarn flow fabric
```
DiffTrain build for commit https://github.com/facebook/react/commit/425f72bdd4263dcaba829cf52545309bbfde56b4.
Remove module pattern function component support (flag only)
> This is a redo of #27742, but only including the flag removal,
excluding further simplifications.
The module pattern
```
function MyComponent() {
return {
render() {
return this.state.foo
}
}
}
```
has been deprecated for approximately 5 years now. This PR removes
support for this pattern.
DiffTrain build for commit https://github.com/facebook/react/commit/a73c3450e1b528fa6cb3e94fa4d4359c7a4b61f1.
The module pattern
```
function MyComponent() {
return {
render() {
return this.state.foo
}
}
}
```
has been deprecated for approximately 5 years now. This PR removes
support for this pattern. It also simplifies a number of code paths in
particular related to the concept of `IndeterminateComponent` types.
DiffTrain build for commit https://github.com/facebook/react/commit/cc56bed38cbe5a5c76dfdc4e9c642fab4884a3fc.
Removes the digest property from errorInfo passed to onRecoverableError
when handling an error propagated from the server. Previously we warned
in Dev but still provided the digest on the errorInfo object. This
change removes digest from error info but continues to warn if it is
accessed. The reason for retaining the warning is the version with the
warning was not released as stable but we will include this deprecated
removal in our next major so we should communicate this change at
runtime.
DiffTrain build for commit https://github.com/facebook/react/commit/299a9c0598576f7dba170771b1c0b821281b1e15.
We didn't recover after all.
Currently we might log a recoverable error in the recovery pass. E.g.
the SSR server had an error. Then the client component fails to render
which errors again. This ends up double logging.
So if we fail to actually complete a fully successful commit, we ignore
any recoverable errors because we'll get real errors logged.
It's possible that this might cover up some other error that happened at
the same time.
DiffTrain build for commit https://github.com/facebook/react/commit/e10a7b5cd541882a78ff659147c1a0294413ccb0.
Stacked on #28627.
This makes error logging configurable using these
`createRoot`/`hydrateRoot` options:
```
onUncaughtError(error: mixed, errorInfo: {componentStack?: ?string}) => void
onCaughtError(error: mixed, errorInfo: {componentStack?: ?string, errorBoundary?: ?React.Component<any, any>}) => void
onRecoverableError(error: mixed, errorInfo: {digest?: ?string, componentStack?: ?string}) => void
```
We already have the `onRecoverableError` option since before.
Overriding these can be used to implement custom error dialogs (with
access to the `componentStack`).
It can also be used to silence caught errors when testing an error
boundary or if you prefer not getting logs for caught errors that you've
already handled in an error boundary.
I currently expose the error boundary instance but I think we should
probably remove that since it doesn't make sense for non-class error
boundaries and isn't very useful anyway. It's also unclear what it
should do when an error is rethrown from one boundary to another.
Since these are public APIs now we can implement the
ReactFiberErrorDialog forks using these options at the roots of the
builds. So I unforked those files and instead passed a custom option for
the native and www builds.
To do this I had to fork the ReactDOMLegacy file into ReactDOMRootFB
which is a duplication but that will go away as soon as the FB fork is
the only legacy root.
DiffTrain build for commit https://github.com/facebook/react/commit/a0537160771bafae90c6fd3154eeead2f2c903e7.
Stacked on top of #28498 for test fixes.
### Don't Rethrow
When we started React it was 1:1 setState calls a series of renders and
if they error, it errors where the setState was called. Simple. However,
then batching came and the error actually got thrown somewhere else.
With concurrent mode, it's not even possible to get setState itself to
throw anymore.
In fact, all APIs that can rethrow out of React are executed either at
the root of the scheduler or inside a DOM event handler.
If you throw inside a React.startTransition callback that's sync, then
that will bubble out of the startTransition but if you throw inside an
async callback or a useTransition we now need to handle it at the hook
site. So in 19 we need to make all React.startTransition swallow the
error (and report them to reportError).
The only one remaining that can throw is flushSync but it doesn't really
make sense for it to throw at the callsite neither because batching.
Just because something rendered in this flush doesn't mean it was
rendered due to what was just scheduled and doesn't mean that it should
abort any of the remaining code afterwards. setState is fire and forget.
It's send an instruction elsewhere, it's not part of the current
imperative code.
Error boundaries never rethrow. Since you should really always have
error boundaries, most of the time, it wouldn't rethrow anyway.
Rethrowing also actually currently drops errors on the floor since we
can only rethrow the first error, so to avoid that we'd need to call
reportError anyway. This happens in RN events.
The other issue with rethrowing is that it logs an extra console.error.
Since we're not sure that user code will actually log it anywhere we
still log it too just like we do with errors inside error boundaries
which leads all of these to log twice.
The goal of this PR is to never rethrow out of React instead, errors
outside of error boundaries get logged to reportError. Event system
errors too.
### Breaking Changes
The main thing this affects is testing where you want to inspect the
errors thrown. To make it easier to port, if you're inside `act` we
track the error into act in an aggregate error and then rethrow it at
the root of `act`. Unlike before though, if you flush synchronously
inside of act it'll still continue until the end of act before
rethrowing.
I expect most user code breakages would be to migrate from `flushSync`
to `act` if you assert on throwing.
However, in the React repo we also have `internalAct` and the
`waitForThrow` helpers. Since these have to use public production
implementations we track these using the global onerror or process
uncaughtException. Unlike regular act, includes both event handler
errors and onRecoverableError by default too. Not just render/commit
errors. So I had to account for that in our tests.
We restore logging an extra log for uncaught errors after the main log
with the component stack in it. We use `console.warn`. This is not yet
ignorable if you preventDefault to the main error event. To avoid
confusion if you don't end up logging the error to console I just added
`An error occurred`.
### Polyfill
All browsers we support really supports `reportError` but not all test
and server environments do, so I implemented a polyfill for browser and
node in `shared/reportGlobalError`. I don't love that this is included
in all builds and gets duplicated into isomorphic even though it's not
actually needed in production. Maybe in the future we can require a
polyfill for this.
### Follow Ups
In a follow up, I'll make caught vs uncaught error handling be
configurable too.
---------
Co-authored-by: Ricky Hanlon <rickhanlonii@gmail.com>
DiffTrain build for commit https://github.com/facebook/react/commit/6786563f3cbbc9b16d5a8187207b5bd904386e53.
Stacked on #28502.
This builds on the mechanism in #28502 by adding a diff of everything
we've collected so far to the thrown error or logged error.
This isn't actually a longest common subsequence diff. This means that
there are certain cases that can appear confusing such as a node being
added/removed when it really would've appeared later in the list. In
fact once a node mismatches, we abort rendering so we don't have the
context of what would've been rendered. It's not quite right to use the
result of the recovery render because it can use client-only code paths
using useSyncExternalStore which would yield false differences. That's
why diffing the HTML isn't quite right.
I also present abstract components in the stack, these are presented
with the client props and no diff since we don't have the props that
were on the server. The lack of difference might be confusing but it's
useful for context.
The main thing that's data new here is that we're adding some siblings
and props for context.
Examples in the [snapshot
commit](https://github.com/facebook/react/pull/28512/commits/e14532fd8df858a319d3eca687d83227209a498c).
DiffTrain build for commit https://github.com/facebook/react/commit/2ec2aaea98588178525f83495669e11e96815a00.
Stacked on #28476.
We used to `console.error` for every mismatch we found, up until the
error we threw for the hydration mismatch.
This changes it so that we build up a set of diffs up until we either
throw or complete hydrating the root/suspense boundary. If we throw, we
append the diff to the error message which gets passed to
onRecoverableError (which by default is also logged to console). If we
complete, we append it to a `console.error`.
Since we early abort when something throws, it effectively means that we
can only collect multiple diffs if there were preceding non-throwing
mismatches - i.e. only properties mismatched but tag name matched.
There can still be multiple logs if multiple siblings Suspense
boundaries all error hydrating but then they're separate errors
entirely.
We still log an extra line about something erroring but I think the goal
should be that it leads to a single recoverable or console.error.
This doesn't yet actually print the diff as part of this message. That's
in a follow up PR.
DiffTrain build for commit https://github.com/facebook/react/commit/f7aa5e0aa3e2aa51279af4b6cb5413912cacd7f5.
Stacked on #28458.
This doesn't actually really change the messages yet, it's just a
refactor.
Hydration warnings can be presented either as HTML or React JSX format.
If presented as HTML it makes more sense to make that a DOM specific
concept, however, I think it's actually better to present it in terms of
React JSX.
Most of the time the errors aren't going to be something messing with
them at the HTML/HTTP layer. It's because the JS code does something
different. Most of the time you're working in just React. People don't
necessarily even know what the HTML form of it looks like. So this takes
the approach that the warnings are presented in React JSX in their rich
object form.
Therefore, I'm moving the approach to yield diff data to the reconciler
but it's the reconciler that's actually printing all the warnings.
DiffTrain build for commit https://github.com/facebook/react/commit/4b8dfd6215bf855402ae1a94cb0ae4f467afca9a.
While Meta is still using legacy mode and we can't remove completely,
Meta is not using legacy hydration so we should be able to remove that.
This is just the first step. Once removed, we can vastly simplify the
DOMConfig for hydration.
This will have to be rebased when tests are upgraded.
DiffTrain build for commit https://github.com/facebook/react/commit/670d61bea23470e980ba13c1c8441e375779b0b8.
## Overview
_Depends on https://github.com/facebook/react/pull/28514_
This PR adds a new React hook called `useActionState` to replace and
improve the ReactDOM `useFormState` hook.
## Motivation
This hook intends to fix some of the confusion and limitations of the
`useFormState` hook.
The `useFormState` hook is only exported from the `ReactDOM` package and
implies that it is used only for the state of `<form>` actions, similar
to `useFormStatus` (which is only for `<form>` element status). This
leads to understandable confusion about why `useFormState` does not
provide a `pending` state value like `useFormStatus` does.
The key insight is that the `useFormState` hook does not actually return
the state of any particular form at all. Instead, it returns the state
of the _action_ passed to the hook, wrapping it and returning a
trackable action to add to a form, and returning the last returned value
of the action given. In fact, `useFormState` doesn't need to be used in
a `<form>` at all.
Thus, adding a `pending` value to `useFormState` as-is would thus be
confusing because it would only return the pending state of the _action_
given, not the `<form>` the action is passed to. Even if we wanted to
tie them together, the returned `action` can be passed to multiple
forms, creating confusing and conflicting pending states during multiple
form submissions.
Additionally, since the action is not related to any particular
`<form>`, the hook can be used in any renderer - not only `react-dom`.
For example, React Native could use the hook to wrap an action, pass it
to a component that will unwrap it, and return the form result state and
pending state. It's renderer agnostic.
To fix these issues, this PR:
- Renames `useFormState` to `useActionState`
- Adds a `pending` state to the returned tuple
- Moves the hook to the `'react'` package
## Reference
The `useFormState` hook allows you to track the pending state and return
value of a function (called an "action"). The function passed can be a
plain JavaScript client function, or a bound server action to a
reference on the server. It accepts an optional `initialState` value
used for the initial render, and an optional `permalink` argument for
renderer specific pre-hydration handling (such as a URL to support
progressive hydration in `react-dom`).
Type:
```ts
function useActionState<State>(
action: (state: Awaited<State>) => State | Promise<State>,
initialState: Awaited<State>,
permalink?: string,
): [state: Awaited<State>, dispatch: () => void, boolean];
```
The hook returns a tuple with:
- `state`: the last state the action returned
- `dispatch`: the method to call to dispatch the wrapped action
- `pending`: the pending state of the action and any state updates
contained
Notably, state updates inside of the action dispatched are wrapped in a
transition to keep the page responsive while the action is completing
and the UI is updated based on the result.
## Usage
The `useActionState` hook can be used similar to `useFormState`:
```js
import { useActionState } from "react"; // not react-dom
function Form({ formAction }) {
const [state, action, isPending] = useActionState(formAction);
return (
<form action={action}>
<input type="email" name="email" disabled={isPending} />
<button type="submit" disabled={isPending}>
Submit
</button>
{state.errorMessage && <p>{state.errorMessage}</p>}
</form>
);
}
```
But it doesn't need to be used with a `<form/>` (neither did
`useFormState`, hence the confusion):
```js
import { useActionState, useRef } from "react";
function Form({ someAction }) {
const ref = useRef(null);
const [state, action, isPending] = useActionState(someAction);
async function handleSubmit() {
// See caveats below
await action({ email: ref.current.value });
}
return (
<div>
<input ref={ref} type="email" name="email" disabled={isPending} />
<button onClick={handleSubmit} disabled={isPending}>
Submit
</button>
{state.errorMessage && <p>{state.errorMessage}</p>}
</div>
);
}
```
## Benefits
One of the benefits of using this hook is the automatic tracking of the
return value and pending states of the wrapped function. For example,
the above example could be accomplished via:
```js
import { useActionState, useRef } from "react";
function Form({ someAction }) {
const ref = useRef(null);
const [state, setState] = useState(null);
const [isPending, setIsPending] = useTransition();
function handleSubmit() {
startTransition(async () => {
const response = await someAction({ email: ref.current.value });
setState(response);
});
}
return (
<div>
<input ref={ref} type="email" name="email" disabled={isPending} />
<button onClick={handleSubmit} disabled={isPending}>
Submit
</button>
{state.errorMessage && <p>{state.errorMessage}</p>}
</div>
);
}
```
However, this hook adds more benefits when used with render specific
elements like react-dom `<form>` elements and Server Action. With
`<form>` elements, React will automatically support replay actions on
the form if it is submitted before hydration has completed, providing a
form of partial progressive enhancement: enhancement for when javascript
is enabled but not ready.
Additionally, with the `permalink` argument and Server Actions,
frameworks can provide full progressive enhancement support, submitting
the form to the URL provided along with the FormData from the form. On
submission, the Server Action will be called during the MPA navigation,
similar to any raw HTML app, server rendered, and the result returned to
the client without any JavaScript on the client.
## Caveats
There are a few Caveats to this new hook:
**Additional state update**: Since we cannot know whether you use the
pending state value returned by the hook, the hook will always set the
`isPending` state at the beginning of the first chained action,
resulting in an additional state update similar to `useTransition`. In
the future a type-aware compiler could optimize this for when the
pending state is not accessed.
**Pending state is for the action, not the handler**: The difference is
subtle but important, the pending state begins when the return action is
dispatched and will revert back after all actions and transitions have
settled. The mechanism for this under the hook is the same as
useOptimisitic.
Concretely, what this means is that the pending state of
`useActionState` will not represent any actions or sync work performed
before dispatching the action returned by `useActionState`. Hopefully
this is obvious based on the name and shape of the API, but there may be
some temporary confusion.
As an example, let's take the above example and await another action
inside of it:
```js
import { useActionState, useRef } from "react";
function Form({ someAction, someOtherAction }) {
const ref = useRef(null);
const [state, action, isPending] = useActionState(someAction);
async function handleSubmit() {
await someOtherAction();
// The pending state does not start until this call.
await action({ email: ref.current.value });
}
return (
<div>
<input ref={ref} type="email" name="email" disabled={isPending} />
<button onClick={handleSubmit} disabled={isPending}>
Submit
</button>
{state.errorMessage && <p>{state.errorMessage}</p>}
</div>
);
}
```
Since the pending state is related to the action, and not the handler or
form it's attached to, the pending state only changes when the action is
dispatched. To solve, there are two options.
First (recommended): place the other function call inside of the action
passed to `useActionState`:
```js
import { useActionState, useRef } from "react";
function Form({ someAction, someOtherAction }) {
const ref = useRef(null);
const [state, action, isPending] = useActionState(async (data) => {
// Pending state is true already.
await someOtherAction();
return someAction(data);
});
async function handleSubmit() {
// The pending state starts at this call.
await action({ email: ref.current.value });
}
return (
<div>
<input ref={ref} type="email" name="email" disabled={isPending} />
<button onClick={handleSubmit} disabled={isPending}>
Submit
</button>
{state.errorMessage && <p>{state.errorMessage}</p>}
</div>
);
}
```
For greater control, you can also wrap both in a transition and use the
`isPending` state of the transition:
```js
import { useActionState, useTransition, useRef } from "react";
function Form({ someAction, someOtherAction }) {
const ref = useRef(null);
// isPending is used from the transition wrapping both action calls.
const [isPending, startTransition] = useTransition();
// isPending not used from the individual action.
const [state, action] = useActionState(someAction);
async function handleSubmit() {
startTransition(async () => {
// The transition pending state has begun.
await someOtherAction();
await action({ email: ref.current.value });
});
}
return (
<div>
<input ref={ref} type="email" name="email" disabled={isPending} />
<button onClick={handleSubmit} disabled={isPending}>
Submit
</button>
{state.errorMessage && <p>{state.errorMessage}</p>}
</div>
);
}
```
A similar technique using `useOptimistic` is preferred over using
`useTransition` directly, and is left as an exercise to the reader.
## Thanks
Thanks to @ryanflorence @mjackson @wesbos
(https://github.com/facebook/react/issues/27980#issuecomment-1960685940)
and [Allan
Lasser](https://allanlasser.com/posts/2024-01-26-avoid-using-reacts-useformstatus)
for their feedback and suggestions on `useFormStatus` hook.
DiffTrain build for commit https://github.com/facebook/react/commit/5c65b27587c0507d66a84e055de948fc62d471d4.
Currently you can accidentally pass React Element to a Server Action. It
warns but in prod it actually works because we can encode the symbol and
otherwise it's mostly a plain object. It only works if you only pass
host components and no function props etc. which makes it potentially
error later. The first thing this does it just early hard error for
elements.
I made Lazy work by unwrapping though since that will be replaced by
Promises later which works.
Our protocol is not fully symmetric in that elements flow from Server ->
Client. Only the Server can resolve Components and only the client
should really be able to receive host components. It's not intended that
a Server can actually do something with them other than passing them to
the client.
In the case of a Reply, we expect the client to be stateful. It's
waiting for a response. So anything we can't serialize we can still pass
by reference to an in memory object. So I introduce the concept of a
TemporaryReferenceSet which is an opaque object that you create before
encoding the reply. This then stashes any unserializable values in this
set and encode the slot by id. When a new response from the Action then
returns we pass the same temporary set into the parser which can then
restore the objects. This lets you pass a value by reference to the
server and back into another slot.
For example it can be used to render children inside a parent tree from
a server action:
```
export async function Component({ children }) {
"use server";
return <div>{children}</div>;
}
```
(You wouldn't normally do this due to the waterfalls but for advanced
cases.)
A common scenario where this comes up accidentally today is in
`useActionState`.
```
export function action(state, formData) {
"use server";
if (errored) {
return <div>This action <strong>errored</strong></div>;
}
return null;
}
```
```
const [errors, formAction] = useActionState(action);
return <div>{errors}<div>;
```
It feels like I'm just passing the JSX from server to client. However,
because `useActionState` also sends the previous state *back* to the
server this should not actually be valid. Before this PR this actually
worked accidentally. You get a DEV warning but it used to work in prod.
Once you do something like pass a client reference it won't work tho. We
could perhaps make client references work by stashing where we got them
from but it wouldn't work with all possible JSX.
By adding temporary references to the action implementation this will
work again - on the client. It'll also be more efficient since we don't
send back the JSX content that you shouldn't introspect on the server
anyway.
However, a flaw here is that the progressive enhancement of this case
won't work because we can't use temporary references for progressive
enhancement since there's no in memory stash. What is worse is that it
won't error if you hydrate. ~It also will error late in the example
above because the first state is "undefined" so invoking the form once
works - it errors on the second attempt when it tries to send the error
state back again.~ It actually errors on the first invocation because we
need to eagerly serialize "previous state" into the form. So at least
that's better.
I think maybe the solution to this particular pattern would be to allow
JSX to serialize if you have no temporary reference set, and remember
client references so that client references can be returned back to the
server as client references. That way anything you could send from the
server could also be returned to the server. But it would only deopt to
serializing it for progressive enhancement. The consequence of that
would be that there's a lot of JSX that might accidentally seem like it
should work but it's only if you've gotten it from the server before
that it works. This would have to have pair them somehow though since
you can't take a client reference from one implementation of Flight and
use it with another.
DiffTrain build for commit https://github.com/facebook/react/commit/83409a1fdd14b2e5b33c587935a7ef552607780f.
With this change, the different files in RN will have *different*
hashes. This replaces the git hash and means that the file content
(including version) is only updated when the rest of the file content
actually changes. This should remove "noop" changes that need to be
synced that only update the version string.
A difference to the www implementation here is (and I'd be looking at
updating www as well if this lands well) that each file has an
individual hash instead of a combined content hash. This further reduces
the number of updated files and I couldn't find a reason we need to have
these in sync. The best I can gather is that this hash is used so folks
don't directly compare version string and make future updates harder.
DiffTrain build for commit https://github.com/facebook/react/commit/cb076b593cec3a92338958f58468cce19cb8f0d9.