adobe / adobe/react-spectrum

Collection components in the tab row throw; trigger guards can't cover triggerless popovers

Open
#10,368 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
TypeScript
Stars
15.9k
Forks
1.6k
Avg merge
3d 9m
Merged PRs (30d)
59

Description

### Provide a general summary of the issue here

Collection components rendered alongside `TabList` inside `Tabs` throw, because they render during the `Tabs` collection's hidden pass and contribute items to the `Tabs` document instead of building their own.

#10019 addressed this by making `MenuTrigger`, `Select` and `ComboBox` hideable, and #10367 adds `DialogTrigger`. Two shapes remain that a per-trigger guard doesn't reach — and the second can't be reached by that strategy at all, because there is no trigger component to guard.

### 🤔 Expected Behavior?

Collection components in the tab row build their own collection and render normally.

### 😯 Current Behavior

**1. A `TagGroup` in the tab row** (e.g. chips for currently-applied filters, beside the tabs):

```
TypeError: Cannot destructure property 'onAction' of '_utils.listMap.get(...)' as it is undefined.
```

**2. A standalone controlled `Popover`** (`triggerRef` + `isOpen`, no trigger component) containing a `Menu`:

```
TypeError: Cannot read properties of null (reading 'isDisabled') // useMenuItem.ts:173
```

The second is the interesting one: the hideable-trigger approach can't cover it, since the popover is positioned via `triggerRef` rather than wrapped in a `MenuTrigger`/`DialogTrigger`.

Reduced further, a bare `Menu`, `ListBox` or `GridList` as a sibling of `TabList` throws for the same reason (three different internal errors). Those are minimal reductions rather than realistic usage — the two above are the ones I would actually expect people to write.

### 💁 Possible Solution

No PR, since this is a design call rather than a patch.

The per-trigger guards stop the *gateway* rendering. The underlying rule — a collection component should not contribute items to an unrelated ancestor's collection document — isn't enforced anywhere, so each new shape needs its own guard, and shapes without a trigger have nowhere to put one.

`CollectionBuilder` is where the rule could live:

```tsx
// If a document was provided above us, we're already in a hidden tree. Just render the content.
let doc = useContext(CollectionDocumentContext);
if (doc) {
return props.content as ReactElement;
}
```

Correct for a nested collection that genuinely belongs to the ancestor (submenu items joining the parent menu, `Select`'s `ListBox`); wrong for an independent collection root that merely happens to be nested. Distinguishing those — perhaps opt-in from the components that need inheritance — would cover all of these at once.

### 🔦 Context

Found while fixing `DialogTrigger` (#10367) for a tab row containing a filter popover. That PR fixes our case; this issue is only about the shapes it doesn't reach.

### 💻 Code Sample

```jsx
function inTabs(node) {
return (



First Tab
Second Tab

{node}

A
B

);
}

// 1. TagGroup in the tab row
render(inTabs(

Filter A

));

// 2. standalone controlled Popover holding a Menu
function StandalonePopover() {
let ref = useRef(null);
return (
<>
Open

Item 1


);
}
render(inTabs());
```

Control: the identical `Menu` element renders fine with no `Tabs` wrapper, and nested inside plain `div`s — the `Tabs` wrapper is the only variable.

### 🌍 Your Environment

| Software | Version(s) |
| --- | --- |
| react-aria-components | 1.19.0 / `main` (with #10367 applied) |
| Browser | n/a (reproduces in jsdom) |
| Operating System | macOS |

Contributor guide

Open the contributing guide

Research direction

Start by inspecting CollectionBuilder and CollectionDocumentContext, then reproduce the TagGroup and controlled Popover cases from the issue inside Tabs. Determine how independent collection roots can avoid contributing to an ancestor collection without breaking collections that intentionally inherit it, such as submenu items and Select's ListBox. Done means these examples render normally while legitimate nested collection behavior remains intact.

Written by the indexing model from the issue text.

Assessment

Tech stack
react, typescript
Domain
frontend
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Needs clarification
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.