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?

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