Skip to content

Collection components lose all items when re-shown by <Activity> after having been visible (return-visit case, not covered by #9300) #10298

Description

@mlm20

Provide a general summary of the issue here

React Aria Components collections (GridList, TabList, likely all collection components) render empty when their subtree is hidden via React 19.2 <Activity mode="hidden"> after having been visible and populated, and then shown again.

This is the scenario originally reported in #9319, which was closed as a duplicate of #9173 before the fix for #9173 (#9300) was merged. #9300 fixes the starts-hidden, first-reveal case, but the visible, then hidden, then visible-again case still reproduces on the latest release (1.19.0). As far as I can tell it is currently untracked.

This affects every Next.js 16 app using Cache Components: the Next router wraps up to 3 preserved routes in <Activity mode="hidden"> and re-shows the same instance on back-navigation (see Preserving UI state), so any collection component on a route breaks on every return visit.

🤔 Expected Behavior?

Items re-appear when the Activity boundary becomes visible again. The DOM and state were preserved, and the owning component re-renders with the correct items.

😯 Current Behavior

The collection renders empty. All GridListItem rows / Tab elements are gone from the DOM entirely (not merely hidden), while static siblings outside the collection render fine.

Things I verified while debugging:

  • The fix: collections nested within Activities (React 19.2) #9300 fix is present in the shipped react-aria-components@1.19.0 bundle. dist/private/collections/Document.mjs contains the patched queueUpdate (this.collection = this.collection.clone() before notifying subscriptions) and getCollection returning this.collection while inSubscription. So this is not a stale-dependency problem.
  • The props reaching the collection owner are correct on the broken render. I logged item counts on every render and commit; renders and commits both happen with the right data.
  • Forcing a single re-render after reveal fixes it. An effect that runs on the hide-to-visible transition (effects re-run on reveal) and bumps a useState restores all items without remounting:
function useRerenderOnActivityReveal() {
  const [, bump] = useState(0);
  const mounted = useRef(false);
  useEffect(() => {
    if (!mounted.current) { mounted.current = true; return; }
    bump((k) => k + 1); // re-shown after Activity hide
  }, []);
}

This suggests the collection store repopulates during or after reveal but never schedules the render that would make useSyncExternalStore re-read it. That is the same class of problem #9300 fixed for the mount path, but on the reappear path.

💁 Possible Solution

Presumably the reveal path (reappearLayoutEffects: refs and effects re-attach, useSyncExternalStore resubscribes) needs the same "clone so React queues a render" treatment that #9300 applied to queueUpdate, or the store needs to schedule one render on resubscribe when its snapshot changed while hidden.

Happy to test a patch; we can reproduce this reliably.

🔦 Context

We hit this in a production dashboard after enabling Next.js 16 Cache Components. Every list and tab bar in the app disappears whenever a user navigates away from a page and back. We are shipping the re-render-on-reveal hook above as a temporary workaround in our shared component wrappers.

🖥️ Steps to Reproduce

Observed in a Next.js 16 app (cacheComponents: true), where the router drives the Activity boundaries:

  1. Render a route containing a GridList (or Tabs) populated with items. Items render correctly.
  2. Client-side navigate to another route. Next hides the previous route's tree in <Activity mode="hidden">; the DOM is preserved and effects are cleaned up.
  3. Client-side navigate back. Next flips the preserved instance to mode="visible".
  4. The GridList items / TabList tabs are gone from the DOM.

Equivalent pure-React shape (matches what the Next router does):

<Activity mode={show ? 'visible' : 'hidden'}>
  <GridList aria-label="test">
    <GridListItem>One</GridListItem>
    <GridListItem>Two</GridListItem>
  </GridList>
</Activity>
// start with show=true (items render), set show=false, then show=true again: items are gone

Note the difference from #9173's reproduction, which started hidden. The tests added in #9300 cover the starts-hidden case.

Version

react-aria-components 1.19.0 (resolving nested react-aria 3.50.0 / react-stately 3.48.0), React 19.2.7

What browsers are you seeing the problem on?

Chrome

What operating system are you using?

macOS

🧢 Your Company/Team

Terra (tryterra.co)

🕷 Tracking Issue

n/a

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingreact 19

    Type

    Fields

    No fields configured for Bug.

    Projects

    Status
    📋 Waiting for Sprint

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions