Skip to content

Accordion opens the last item on initial mount instead of the first #861

Description

@Shinyaigeek

Summary

<Accordion> from @openuidev/react-ui opens the last item on initial mount, even for a fully static (non-streaming) response. Expected is the first item open (as the pre-refactor implementation did), or all items closed.

This is the same defect shape as the <Tabs> issue #819: a streaming "follow the newest content" heuristic that doesn't exclude the initial mount.

Reproduction

Standalone repro (published packages only, npm install && npm run dev):
https://github.com/Shinyaigeek/openui-accordion-repro

root = Accordion([i1, i2, i3])
i1 = AccordionItem("a", "Apple", [c1])
i2 = AccordionItem("b", "Banana", [c2])
i3 = AccordionItem("c", "Cherry", [c3])
c1 = TextContent("Apple section content")
c2 = TextContent("Banana section content")
c3 = TextContent("Cherry section content")
  • Expected: the first section (Apple) is open on mount, or all sections start closed.
  • Actual: the last section (Cherry) is open on mount.

Cherry section open on mount

Versions: @openuidev/react-ui@0.13.1, @openuidev/react-lang@0.2.9, @openuidev/react-headless@0.9.3, React 19.

Root cause

In packages/react-ui/src/genui-lib/Accordion/index.tsx, the auto-open logic runs during render and treats any growth in item count as "a new item arrived during streaming":

const [openItem, setOpenItem] = React.useState<string>("");
const userHasInteracted = React.useRef(false);
const prevItemCount = React.useRef(0);

// Auto-open: only when a NEW item arrives during streaming
if (!userHasInteracted.current && items.length > prevItemCount.current) {
  const newest = items[items.length - 1];
  if (newest) setOpenItem(newest.props.value);
}
prevItemCount.current = items.length;

On the very first render prevItemCount.current is 0, so a fully static N-item accordion satisfies items.length > 0 and the branch opens items[items.length - 1] — the last section. The baseline pass is indistinguishable from streaming growth.

The earlier implementation had a mount effect that opened the first item; the refactor in #485 removed it, leaving only the auto-open-newest branch.

Related streaming latch in the same branch

While actually streaming there is a second problem: when a new item appears before its value string has finished streaming, setOpenItem latches a prefix of the value (e.g. "ov" for "overview"). The only re-sync trigger is item-count growth, so the completed value never re-syncs; the latched prefix matches no item and the accordion ends up with no section open until the user clicks. (Items are not filtered for complete values — contrast Tabs, which filters value != null.)

Suggested fix

Mirror the Tabs fix (c2b1e7e for #819):

  • Skip the auto-open pass on the baseline render (e.g. seed prevItemCount from the first non-empty items.length), so a static mount opens the first item (or none) instead of the last.
  • Only auto-open items whose value is complete, and re-sync when a previously-partial value completes, so the streaming auto-open doesn't latch a prefix.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions