You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{/* Alway render the sentinel. For now onus is on the user for styling when using flex + gap (this would introduce a gap even though it doesn't take room) */}
579
+
{/* @ts-ignore - compatibility with React < 19 */}
@@ -589,22 +602,73 @@ export const ListBoxLoadMoreItem = createLeafComponent(LoaderNode, function List
589
602
};
590
603
591
604
return(
592
-
<>
593
-
{/* Alway render the sentinel. For now onus is on the user for styling when using flex + gap (this would introduce a gap even though it doesn't take room) */}
594
-
{/* @ts-ignore - compatibility with React < 19 */}
Copy file name to clipboardExpand all lines: rfcs/2026-async-react.md
+74-11Lines changed: 74 additions & 11 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -19,13 +19,15 @@ In this RFC, we propose adding support for React's action prop pattern to React
19
19
20
20
## Motivation
21
21
22
-
At React Conf 2025, the React core team [presented](https://www.youtube.com/watch?v=B_2E96URooA) their vision of "Async React". Using features introduced in React 19 such as [useTransition](https://react.dev/reference/react/useTransition), [useOptimistic](https://react.dev/reference/react/useOptimistic), and [Suspense](https://react.dev/reference/react/Suspense) for data fetching, React can now coordinate loading states across an entire app, and reduce the amount of code needed to handle data loading edge cases. This improves the user experience by making loading/saving states in-line with the component that triggered the update.
22
+
At React Conf 2025, the React core team [presented](https://www.youtube.com/watch?v=B_2E96URooA) their vision of "Async React". Using features introduced in React 19 such as [useTransition](https://react.dev/reference/react/useTransition), [useOptimistic](https://react.dev/reference/react/useOptimistic), and [Suspense](https://react.dev/reference/react/Suspense) for data fetching, React can now coordinate pending states across an entire app, and reduce the amount of code needed to handle data fetching edge cases. This improves the user experience by making loading/saving states in-line with the component that triggered the update.
23
23
24
-
While these React hooks are usable today, they require some boilerplate to set up. This can be simplified by introducing the [action prop](https://react.dev/reference/react/useTransition#exposing-action-props-from-components) pattern. By convention, action props are automatically wrapped in React's `startTransition` function and may include a pending state within the component that triggered them. This way the application doesn't need to handle these states themselves since it's handled by the component library.
24
+
While these React features are usable today, they require some boilerplate to set up. This can be simplified by introducing the [action prop](https://react.dev/reference/react/useTransition#exposing-action-props-from-components) pattern. By convention, action props are automatically wrapped in React's `startTransition` function and may include a pending state within the component that triggered them. This way the application doesn't need to handle these states themselves since it's handled by the component library.
25
25
26
26
## Detailed Design
27
27
28
-
This RFC proposes adding support for action props directly to React Aria Components. While it's possible to introduce these at a higher level (e.g. in a design system), pending states have accessibility requirements to ensure clear announcements for screen readers, focus management, etc. In addition, multiple design systems can benefit from handling pending states at a lower level layer.
28
+
This RFC proposes two new features: built-in action props, and improved support for data fetching with Suspense. While it's possible to introduce these at a higher level (e.g. in a design system), pending and error states have accessibility requirements to ensure clear announcements for screen readers, focus management, etc. In addition, multiple design systems can benefit from handling pending states at a lower level layer.
29
+
30
+
### Action props
29
31
30
32
Action props will correspond to events, either using the `action` name for simple actions (e.g. Button) or the `Action` suffix (e.g. `changeAction`). These accept an `async` function, which is called within React's `startTransition` function. Each component supporting actions will expose an `isPending` render prop and `data-pending` DOM attribute. This will be used to render a `<ProgressBar>`, associated with the element via ARIA attributes. We will also handle announcing the state change via an ARIA live region.
31
33
@@ -35,7 +37,7 @@ To implement this, we can create a new hook that wraps `useControlledState` and
35
37
36
38
We will also catch errors that are thrown by actions and expose them as an `actionError` render prop, or via the `FieldError` component, enabling [in-line contextual error UIs](https://x.com/devongovett/status/1989788456751697958). This will help reduce over-reliance on toasts as a catch-all way of handling errors in applications by making inline errors just as easy to implement.
37
39
38
-
All together, this significantly simplifies the implementation of loading states and error handling for component libraries and applications. Simply render a `<ProgressBar>` when `isPending` is true, add an async function as an action prop, and React Aria handles the rest.
40
+
All together, this significantly simplifies the implementation of pending states and error handling for component libraries and applications. Simply render a `<ProgressBar>` when `isPending` is true, add an async function as an action prop, and React Aria handles the rest.
39
41
40
42
Here's a potential list of components that could support actions:
41
43
@@ -57,12 +59,13 @@ Here's a potential list of components that could support actions:
57
59
* Select - `changeAction`
58
60
* Slider - `changeAction`
59
61
* Switch - `changeAction` (only when using `SwitchField`, introduced in [#9877](https://github.com/adobe/react-spectrum/pull/9877))
62
+
* Table – `sortAction` (progress should show within the sorted column header)
60
63
* Tabs - `selectionAction`
61
64
* TextField - `changeAction`
62
65
* TimeField - `changeAction`
63
66
* ToggleButton - `changeAction`
64
67
65
-
### Examples
68
+
Below are some examples of some common patterns.
66
69
67
70
#### Pending button
68
71
@@ -211,12 +214,6 @@ function App() {
211
214
212
215
When an error is thrown in a form's `submitAction`, it will be available via the `actionError` render prop. This can be displayed to the user by rendering an `<Alert>`, which will be focused and announced by screen readers. For field-level errors (e.g. server validation), a special error object compatible with [Standard Schema](https://standardschema.dev/schema) could be supported, allowing these errors to be automatically propagated to the correct fields (as we support via the `validationErrors` prop today).
213
216
214
-
**Note**: This proposes a separate `submitAction` prop rather than overloading the existing `action` prop supported by React. `submitAction` has a few differences from `action`:
215
-
216
-
* Errors thrown during the action are caught and passed to the `actionError` render prop.
217
-
* The pending state is automatically passed to the form's submit button. Alternatively we could use React's [useFormStatus](https://react.dev/reference/react-dom/hooks/useFormStatus) hook for that, but this has [bugs](https://github.com/facebook/react/issues/30368) at the moment.
218
-
* The form is not automatically reset after the action completes. This is a [controversial](https://github.com/facebook/react/issues/29034) behavior that is often unwanted (e.g. when errors occur). If a reset is desired, it can be triggered manually via `ReactDOM.requestFormReset`.
219
-
220
217
```tsx
221
218
function App() {
222
219
return (
@@ -252,6 +249,72 @@ function App() {
252
249
}
253
250
```
254
251
252
+
**Note**: This proposes a separate `submitAction` prop rather than overloading the existing `action` prop supported by React. `submitAction` has a few differences from `action`:
253
+
254
+
* Errors thrown during the action are caught and passed to the `actionError` render prop.
255
+
* The pending state is automatically passed to the form's submit button. Alternatively we could use React's [useFormStatus](https://react.dev/reference/react-dom/hooks/useFormStatus) hook for that, but this has [bugs](https://github.com/facebook/react/issues/30368) at the moment.
256
+
* The form is not automatically reset after the action completes. This is a [controversial](https://github.com/facebook/react/issues/29034) behavior that is often unwanted (e.g. when errors occur). If a reset is desired, it can be triggered manually via `ReactDOM.requestFormReset`.
257
+
258
+
### Suspense
259
+
260
+
Today, we support initial loading states in our collection components through `renderEmptyState` with externally controlled state management. Infinite loading is done via collection-specific components, e.g. `ListBoxLoadMoreItem` which trigger their `onLoadMore` callback when scrolled into view. State management is entirely left to external data fetching libraries (e.g. our `useAsyncList` hook).
261
+
262
+
With Suspense, we can simplify this by building loading states into our collection components. Here's what an infinite loading ListBox could look like:
In this example, `ListBoxSuspense` works like `React.Suspense` but with a few additions:
304
+
305
+
* It supports `loading="lazy"`, which renders its children only once it is near the viewport.
306
+
* It includes an error boundary when `renderError` is provided.
307
+
* It wraps its fallback/error in an appropriate element to ensure the accessibility tree is valid (e.g. `<div role="option">`).
308
+
309
+
TBD whether a separate component per collection is necessary, or if we could somehow ensure the correct accessibility wrapper is added automatically.
310
+
311
+
There are several benefits to using Suspense for data fetching instead of an external hook:
312
+
313
+
* It is declarative. External loading and error states must be manually passed around and rendered. Suspense allows design systems and component libraries to build in these states automatically, no matter the data source.
314
+
* It is composable. Different sections within a collection can load from different data sources. Apps can decide whether to make those have a single loading state or separate ones.
315
+
* It will wait for nested parts of the UI to become ready. For example, if each list item contained an image, the list could wait to display the images together instead of popping in one by one.
316
+
* It supports streaming data from the server with React Server Components, enabling fetching to start earlier.
317
+
255
318
## Documentation
256
319
257
320
We'll add new examples to our documentation showing how to use action props, and add pending states to components in our starter kits.
0 commit comments