Skip to content

rfc: Adopting Async React in React Aria Components#9894

Draft
devongovett wants to merge 12 commits intomainfrom
async-react
Draft

rfc: Adopting Async React in React Aria Components#9894
devongovett wants to merge 12 commits intomainfrom
async-react

Conversation

@devongovett
Copy link
Copy Markdown
Member

@devongovett devongovett commented Apr 8, 2026

View Rendered RFC

Comment here

This PR also includes a prototype implementation in several components for discussion.

@github-actions github-actions Bot added the RAC label Apr 8, 2026
@devongovett devongovett changed the title Async react rfc: Adopting Async React in React Aria Components Apr 8, 2026
@rspbot
Copy link
Copy Markdown

rspbot commented Apr 9, 2026

@github-actions github-actions Bot added the S2 label Apr 15, 2026
@rspbot
Copy link
Copy Markdown

rspbot commented Apr 15, 2026

@devongovett devongovett force-pushed the async-react branch 2 times, most recently from 738e9d9 to f08bc63 Compare April 15, 2026 18:50
@rspbot
Copy link
Copy Markdown

rspbot commented Apr 15, 2026

Base automatically changed from checkbox-field to main April 23, 2026 00:07
@rspbot
Copy link
Copy Markdown

rspbot commented Apr 23, 2026

Comment thread rfcs/2026-async-react.md
Comment on lines +265 to +276
function Example() {
return (
<ListBox>
<ListBoxSuspense
// Initial loading state
fallback={<ProgressCircle />}
renderError={error => `Error loading data: ${error}`}>
<Page url="https://pokeapi.co/api/v2/pokemon" />
</ListBoxSuspense>
</ListBox>
);
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so just to verify the API: as mentioned below about the composability, a user who wants to have sections that each load from a different data source and have individual spinners would have separate <ListBoxSuspense>s wrapping a different <Page> (or w/e they would call it in their implementation), and each Page would be a Collection that renders that individual section and its async loaded items? I guess the top level suspense wouldn't be necessary unless they wanted a spinner to wait for all of the section to do an initial load?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah exactly. if you wanted a single loading state for all sections, you'd wrap the suspense around the entire list. If you wanted separate loading states for each section, you'd wrap suspense around each section individually.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, I'm kinda wondering about the styling logistics of that. Loader items typically consume very little height, so one would likely always trigger multiple section loads when reaching the end of the first section, while you would typically only want to load the successor? Can probably be solved with styling in userland, but wondering whether there should maybe be something more intuitive to account for that.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I've done in this example with the Page component, you could nest them so that the second section only loads after the first one (if it is in view). Whether you want parallel or sequential loading, separate or a single spinner, is all up to the application / design requirements.

Comment thread rfcs/2026-async-react.md

* Do pending states make sense in all of these components? Supporting these within Spectrum will require input from design.
* How do we want to support pending states that aren't displayed as a progress bar / spinner (e.g. a "shimmer")? We may need to announce something, even if a progress bar is not present in the DOM.
* How do we want to handle components that have both a loading state for data and a pending state for an action? For example, Select and ComboBox support loading states for their list of items, but may also support a changeAction when the user selects an item. Would these both display the same spinner UI?
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ideally, I think we'd want this to be customizable by the user, so perhaps we have additional render props along side isPending/or some way of informing the user what triggered said pending state?

Comment thread rfcs/2026-async-react.md
* Do pending states make sense in all of these components? Supporting these within Spectrum will require input from design.
* How do we want to support pending states that aren't displayed as a progress bar / spinner (e.g. a "shimmer")? We may need to announce something, even if a progress bar is not present in the DOM.
* How do we want to handle components that have both a loading state for data and a pending state for an action? For example, Select and ComboBox support loading states for their list of items, but may also support a changeAction when the user selects an item. Would these both display the same spinner UI?
* For components with multiple actions, do we want individual pending states (e.g. `isChangePending`, `isSubmitPending`) or a single pending state that aggregates these?
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I'd lean towards an aggregated pending state for now?

});
}, [setControlledValue, setOptimisticValue, setOptimisticError, changeAction]);

return [optimisticValue, isPending, setValue, optimisticError];
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: https://react.dev/reference/react/useActionState has the isPending ordered after the setter, might want to do the same?

Comment thread rfcs/2026-async-react.md
Comment on lines +265 to +276
function Example() {
return (
<ListBox>
<ListBoxSuspense
// Initial loading state
fallback={<ProgressCircle />}
renderError={error => `Error loading data: ${error}`}>
<Page url="https://pokeapi.co/api/v2/pokemon" />
</ListBoxSuspense>
</ListBox>
);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, I'm kinda wondering about the styling logistics of that. Loader items typically consume very little height, so one would likely always trigger multiple section loads when reaching the end of the first section, while you would typically only want to load the successor? Can probably be solved with styling in userland, but wondering whether there should maybe be something more intuitive to account for that.

Comment thread rfcs/2026-async-react.md
## Open Questions

* Do pending states make sense in all of these components? Supporting these within Spectrum will require input from design.
* How do we want to support pending states that aren't displayed as a progress bar / spinner (e.g. a "shimmer")? We may need to announce something, even if a progress bar is not present in the DOM.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would have to be a user-supplied component or a very generic announcement though, right? Otherwise how would we pull the appropriate context/msg for announcement?

Copy link
Copy Markdown
Member Author

@devongovett devongovett Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generic announcement is better than no announcement. We could at least use the accessible name of the element that's pending so it could announce something like "Save, pending" if the button's label was "Save". For a more contextual message, perhaps a prop.

Copy link
Copy Markdown
Contributor

@nwidynski nwidynski Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, but that is only if the pending element has an accessible name in the first place. This is the case with all components that useLabel currently, which is mostly fields, but for the ones that dont it might be difficult / breaking to require? Also see this somewhat related issue I opened a while back #7240 (comment)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All components that would support a pending state already require some kind of accessiblity label. Even components that don't use useLabels internally still have a visible label / aria-label.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The vast majority of the proposed ones yeah, because as I meant, they are mostly fields. Some of them are exceptions though, e.g. Tabs, ToggleButton and Disclosure, which neither have a default aria-label nor enforce presence of a aria-label prop or visible label. That can probably be worked around though with some sane defaults, so its not a strict blocker.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All of those require visible labels (children) / aria-labels too. We might not always emit a warning ourselves (that can be improved) but accessibility checkers like aXe definitely will.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants