Skip to content

Conversation

iansan5653
Copy link
Contributor

@iansan5653 iansan5653 commented Sep 23, 2025

Currently ActionBar only supports direct children because it uses Children APIs to find the props to determine what to render in the menu. By switching to a pure context-based approach using a registry that stores all the necessary data, ActionBar can be updated to support deeply nested children, aliased children, fragments, etc.

The children are registered with the same layout effect approach for determining their measurements, but they are now stored in a Map by unique ID rather than a simple array. The map allows them to update themselves if their props change and de-register themselves when they unmount.

The challenge with this approach is preserving the order of the items in the registry vs in the React tree. We need the items in the registry to always be in the same order as the tree so that the ActionBar knows which ones are last (to hide first) and can preserve their order in the overflow menu. For this we rely on Map preserving the insertion order. Since React will always render the tree from start to end, top to bottom, we know we'll receive the elements in the right order on the first render.

However, effect cleanup still presents a challenge. If we don't clean up after ourselves, the ActionBar will never know if an item got removed from the bar. But if we remove items from the map on cleanup, they will get re-added at the end of the map when they update. So instead, I've set the values to null on cleanup and added logic to ignore null values in the component. This preserves the insertion order for the lifetime of the registry.

Caveats: Unfortunately this approach is not perfect. While it's robust to an item getting removed from the list, or new items getting added to the end of the list, it will cause unexpected behavior if a new item is added in the middle or beginning of the list. This is because these items would be added at the end of the registry, rather than in the correct location. I haven't been able to come up with a solution for this -- we'd have to somehow trigger the registry to be rebuilt from scratch when an item is added, but I don't think that's possible without triggering infinite render loops, since items have to be added to build the registry in the first place.

TLDR: This change would allow consumers to wrap ActionBar item components, use aliased components, and otherwise provide indirect children. But the drawback is that it would cause unexpected behavior if new items are added to the beginning or middle after the initial render.

FWIW, I think the drawback may be a reasonable tradeoff. I don't think that adding new items to the middle would be a very common scenario anyway.

Changelog

New

Changed

Removed

Rollout strategy

  • Patch release
  • Minor release
  • Major release; if selected, include a written rollout or migration plan
  • None; if selected, include a brief description as to why

Testing & Reviewing

Merge checklist

Copy link

changeset-bot bot commented Sep 23, 2025

🦋 Changeset detected

Latest commit: ef92ada

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 2 packages
Name Type
@primer/react Major
@primer/styled-react Major

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Copy link
Contributor

👋 Hi, this pull request contains changes to the source code that github/github depends on. If you are GitHub staff, we recommend testing these changes with github/github using the integration workflow. Thanks!

@github-actions github-actions bot added the integration-tests: recommended This change needs to be tested for breaking changes. See https://arc.net/l/quote/tdmpakpm label Sep 23, 2025
continue
//if the last item is a divider
} else if (childWidthArray[index].text === 'divider') {
} else if (child.type === 'divider') {
Copy link
Contributor

Choose a reason for hiding this comment

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

@github-actions github-actions bot requested a deployment to storybook-preview-6902 October 7, 2025 23:25 Abandoned
@pksjce pksjce marked this pull request as ready for review October 7, 2025 23:55
@pksjce pksjce requested a review from a team as a code owner October 7, 2025 23:55
@pksjce pksjce requested review from TylerJDev and Copilot October 7, 2025 23:55
Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR refactors ActionBar from using React's Children APIs to a pure context-based registry system to support deeply nested children, fragments, and aliased components. The change enables consumers to wrap ActionBar items in arbitrary React components while preserving the overflow behavior.

Key changes:

  • Replaces direct children inspection with a context-based child registry system
  • Introduces unique ID-based registration for items to maintain proper ordering
  • Updates the overflow logic to work with the registry instead of direct child elements

Reviewed Changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

File Description
packages/react/src/ActionBar/ActionBar.tsx Core refactor implementing registry-based child management with context API
packages/react/src/ActionBar/ActionBar.test.tsx Comprehensive test coverage for the new registry system scenarios
packages/react/src/ActionBar/ActionBar.stories.tsx New story demonstrating deep child tree capabilities
.changeset/fair-bars-smile.md Major version changeset documenting the nested children support

Comment on lines +250 to +252
<ActionList.LeadingVisual>
<Icon />
</ActionList.LeadingVisual>
Copy link
Preview

Copilot AI Oct 7, 2025

Choose a reason for hiding this comment

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

The Icon component is rendered without checking if it exists. When menuItem.type is 'action', the icon field could be undefined, which would cause a runtime error when trying to render .

Suggested change
<ActionList.LeadingVisual>
<Icon />
</ActionList.LeadingVisual>
{Icon && (
<ActionList.LeadingVisual>
<Icon />
</ActionList.LeadingVisual>
)}

Copilot uses AI. Check for mistakes.

Comment on lines +284 to +291
registerChild(id, {
type: 'action',
label: props['aria-label'] ?? '',
icon: props.icon,
disabled: !!disabled,
onClick: onClick as MouseEventHandler,
width: widthRef.current,
})
Copy link
Preview

Copilot AI Oct 7, 2025

Choose a reason for hiding this comment

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

The onClick is cast to MouseEventHandler without proper type validation. This could mask type mismatches and lead to runtime errors if the onClick prop doesn't match the expected signature.

Copilot uses AI. Check for mistakes.

Comment on lines +188 to +190
// Rapidly trigger re-renders
for (let i = 0; i < 10; i++) {
await user.click(screen.getByText('Increment'))
Copy link
Preview

Copilot AI Oct 7, 2025

Choose a reason for hiding this comment

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

The test performs 10 sequential async clicks which could be slow and potentially flaky. Consider reducing the number of iterations or using a more efficient approach to test rapid re-renders.

Suggested change
// Rapidly trigger re-renders
for (let i = 0; i < 10; i++) {
await user.click(screen.getByText('Increment'))
// Rapidly trigger re-renders using act to batch updates
for (let i = 0; i < 10; i++) {
await act(async () => {
screen.getByText('Increment').click()
})

Copilot uses AI. Check for mistakes.

@pksjce pksjce changed the title [WIP] Update ActionBar to use support deep child trees using pure context Update ActionBar to use support deep child trees using pure context Oct 8, 2025
@pksjce
Copy link
Contributor

pksjce commented Oct 8, 2025

Since no one else is using this component and it is experimental, we don't need an integration test passing. I'm happy for this to be merged. The drawbacks as @iansan5653 suggested are minimal. Thanks for this change!

@pksjce pksjce added this pull request to the merge queue Oct 8, 2025
Merged via the queue into main with commit c395547 Oct 8, 2025
43 checks passed
@pksjce pksjce deleted the ActionBar-support-deep-children branch October 8, 2025 03:53
@primer primer bot mentioned this pull request Oct 7, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
integration-tests: recommended This change needs to be tested for breaking changes. See https://arc.net/l/quote/tdmpakpm
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants