Skip to content

[EuiTreeView] Migrate to a function component - #9932

Open
VitorBernalRodrigues wants to merge 1 commit into
elastic:mainfrom
VitorBernalRodrigues:refactor/eui-tree-view-function-component
Open

[EuiTreeView] Migrate to a function component#9932
VitorBernalRodrigues wants to merge 1 commit into
elastic:mainfrom
VitorBernalRodrigues:refactor/eui-tree-view-function-component

Conversation

@VitorBernalRodrigues

Copy link
Copy Markdown

Summary

Migrates EuiTreeView from a class component to a function component.

  • Replaces class state and lifecycle logic with React hooks.
  • Replaces withEuiTheme with useEuiTheme.
  • Preserves the existing uncontrolled expansion behavior, keyboard navigation, focus behavior, recursive state, IDs, DOM structure, and ARIA semantics.
  • Preserves EuiTreeView.Item.
  • Removes EuiTreeViewClass, as discussed in [EuiTreeView] Migrate from class to function component #9496.
  • Preserves ref support by forwarding refs to the root <ul>.
  • Adds regression coverage for expansion state, IDs, recursion, keyboard navigation, focus, callbacks, and refs.

Closes #9496.

Compatibility

Current Kibana main was audited for EuiTreeView usage.

  • No EuiTreeViewClass consumers were found.
  • No EuiTreeView ref consumers were found.
  • Existing uses of expandByDefault, isExpanded, callbacks, Node, DOM/classes, and EuiTreeView.Item remain compatible.

No source-breaking Kibana usage was identified.

Testing

  • Jest React 18: 18 tests passed
  • Jest React 17: 18 tests passed
  • Cypress React 18: 12 tests passed
  • Cypress React 17: 12 tests passed
  • Axe: 2 tests passed, 0 violations
  • TypeScript passed
  • Storybook TypeScript passed
  • ESLint passed with no errors
  • git diff --check passed
  • Full yarn pre-push passed
  • No snapshots changed

Screenshots

No visual changes are expected.

The migrated component was manually validated in Storybook for mouse interaction, nested expansion, expandByDefault, expansion arrows, keyboard navigation, and focus behavior.

@VitorBernalRodrigues
VitorBernalRodrigues requested a review from a team as a code owner August 19, 2026 17:04
@cla-checker-service

cla-checker-service Bot commented Aug 19, 2026

Copy link
Copy Markdown

💚 CLA has been signed

@github-actions

Copy link
Copy Markdown

👋 Since this is a community submitted pull request, a Buildkite build has not been started automatically. Would an Elastic organization member please verify the contents of this pull request and kick off a build manually?

@github-actions github-actions Bot added the community contribution (Don't delete - used for automation) label Aug 19, 2026
@weronikaolejniczak

Copy link
Copy Markdown
Contributor

@VitorBernalRodrigues could you please sign the CLA before we proceed with the review?

@weronikaolejniczak
weronikaolejniczak self-requested a review August 24, 2026 14:14
@weronikaolejniczak

Copy link
Copy Markdown
Contributor

buildkite test this

@weronikaolejniczak weronikaolejniczak added the skip-changelog Use on PRs to skip changelog requirement (Don't delete - used for automation) label Aug 25, 2026
@infra-vault-gh-plugin-prod

infra-vault-gh-plugin-prod Bot commented Aug 25, 2026

Copy link
Copy Markdown

💔 Build Failed

Failed CI Steps

History

@infra-vault-gh-plugin-prod

Copy link
Copy Markdown

💚 Build Succeeded

Comment on lines +350 to +353
const EuiTreeViewPublicComponent =
EuiTreeViewComponent as React.ForwardRefExoticComponent<
EuiTreeViewProps & React.RefAttributes<HTMLUListElement>
>;

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.

Instead, let's do it like this:

const _EuiTreeView = forwardRef<HTMLUListElement, CommonTreeProps>(...)

_EuiTreeView.displayName = 'EuiTreeView'

export const EuiTreeView = Object.assign(_EuiTreeView, {
  Item: EuiTreeViewItem,
})

This EuiTreeViewPublicComponent breaks our docgen.

const nextButton = nodeButtons[currentIndex + 1] as HTMLElement;
if (nextButton) {
// Enable keyboard navigation
const onKeyDown = (event: React.KeyboardEvent, node: Node) => {

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: Let's destructure all of these from the React import using type keyword, not use the namespace directly. Applies to the whole file.

Comment on lines +126 to +132
const treeIdGeneratorRef = useRef<
ReturnType<typeof htmlIdGenerator> | undefined
>(undefined);
if (treeIdGeneratorRef.current === undefined) {
treeIdGeneratorRef.current = htmlIdGenerator('euiTreeView');
}
const treeIdGenerator = treeIdGeneratorRef.current;

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: We have a dedicated useGeneratedHtmlId hook.

Comment on lines 260 to 261
const styles = euiTreeViewStyles(theme);
const cssStyles = [styles.euiTreeView, styles[display]];

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.

non-blocking: Let's use useEuiMemoizedStyles.

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.

non-blocking: Could we use userEvent here instead of fireEvent?

@weronikaolejniczak

Copy link
Copy Markdown
Contributor

Hey @VitorBernalRodrigues, are you planning on continuing this contribution?

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

Labels

community contribution (Don't delete - used for automation) skip-changelog Use on PRs to skip changelog requirement (Don't delete - used for automation)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[EuiTreeView] Migrate from class to function component

2 participants