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
Copy file name to clipboardExpand all lines: rfcs/2026-async-react.md
+148-2Lines changed: 148 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -33,9 +33,9 @@ Components with state will use `useOptimistic` to update immediately in response
33
33
34
34
To implement this, we can create a new hook that wraps `useControlledState` and also supports action props. When the value setter is called, we start a transition, set the optimistic value, and trigger the change action. We will also continue emit the `onChange` event and support both controlled and uncontrolled state.
35
35
36
-
We could also potentially catch errors that are thrown by actions and expose these as render props, enabling [in-line contextual error UIs](https://x.com/devongovett/status/1989788456751697958).
36
+
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
37
38
-
All together, this significantly simplifies the implementation of loading states 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.
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.
39
39
40
40
Here's a potential list of components that could support actions:
41
41
@@ -50,6 +50,7 @@ Here's a potential list of components that could support actions:
50
50
* DatePicker - `changeAction`
51
51
* DateRangePicker - `changeAction`
52
52
* Disclosure - `expandAction`
53
+
* Form – `submitAction` (add a `FormError` component to display form-level errors)
A search field for a filterable list. Typing in the field causes a state update, and the results list suspends. While the results are loading, the search field displays a spinner and the previous results display in the list and remain interactive.
93
+
94
+
This illustrates that the pending state may display for longer than the action itself if another part of the UI suspends as a result. The `Suspense` wrapping the result list only displays its fallback during the initial load sequence, not when the update is triggered by an action (this is React's default behavior for transitions).
If an error occurs in an action, it is available via the `actionError` render prop. This button has a shake animation when an error occurs, and displays an error icon.
If you didn't want the Button itself to handle errors and wanted to show errors in a different way, you could add a try/catch statement within the action and catch the error there.
149
+
150
+
```diff
151
+
action={async () => {
152
+
+ try {
153
+
await save();
154
+
+ } catch (err) {
155
+
+ showToast(err);
156
+
+ }
157
+
}}
158
+
```
159
+
160
+
In field components, we could also use the existing `FieldError` to show action errors. In this example, if saving a setting failed, the error would be displayed below the checkbox.
161
+
162
+
```tsx
163
+
function App() {
164
+
return (
165
+
<CheckboxField
166
+
changeAction={async (isSelected) => {
167
+
try {
168
+
awaitsaveSetting(isSelected);
169
+
} catch {
170
+
throw'Failed to save setting.';
171
+
}
172
+
}}>
173
+
<CheckboxButton>Setting</CheckboxButton>
174
+
<FieldError />
175
+
</CheckboxField>
176
+
);
177
+
}
178
+
```
179
+
180
+
**Note**: Errors are only caught when they occur within the action itself, not if another component suspends as a result. This makes sense from a UI perspective: if an error occurred while loading something, it should display where the results would have been (via an error boundary). If it occurred while saving something, it should display where the action was initiated.
181
+
182
+
#### In a design system
183
+
184
+
In a design system such as Spectrum, the loading and error states in the above examples would be built-in. This means application code does not need to worry these states at all.
0 commit comments