-
Notifications
You must be signed in to change notification settings - Fork 655
perf(SelectPanel): memoize overlay props and callbacks #7621
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| '@primer/react': patch | ||
| --- | ||
|
|
||
| Memoize SelectPanel overlayProps, focusTrapSettings, and preventBubbling to reduce allocations on re-renders |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -1,5 +1,5 @@ | ||||||
| import {SearchIcon, TriangleDownIcon, XIcon, type IconProps} from '@primer/octicons-react' | ||||||
| import React, {useCallback, useEffect, useMemo, useRef, useState, type KeyboardEventHandler, type JSX} from 'react' | ||||||
| import React, {useCallback, useEffect, useMemo, useRef, useState, type JSX} from 'react' | ||||||
| import type {AnchoredOverlayProps} from '../AnchoredOverlay' | ||||||
| import {AnchoredOverlay} from '../AnchoredOverlay' | ||||||
| import type {AnchoredOverlayWrapperAnchorProps} from '../AnchoredOverlay/AnchoredOverlay' | ||||||
|
|
@@ -146,6 +146,8 @@ const focusZoneSettings: Partial<FocusZoneHookSettings> = { | |||||
| disabled: true, | ||||||
| } | ||||||
|
|
||||||
| const closeButtonProps = {'aria-label': 'Cancel and close'} | ||||||
|
|
||||||
| const areItemsEqual = (itemA: ItemInput, itemB: ItemInput) => { | ||||||
| // prefer checking equivality by item.id | ||||||
| if (typeof itemA.id !== 'undefined') return itemA.id === itemB.id | ||||||
|
|
@@ -698,9 +700,7 @@ function Panel({ | |||||
| } | ||||||
| }, [open, resetSort]) | ||||||
|
|
||||||
| const focusTrapSettings = { | ||||||
| initialFocusRef: inputRef || undefined, | ||||||
| } | ||||||
| const focusTrapSettings = useMemo(() => ({initialFocusRef: inputRef || undefined}), [inputRef]) | ||||||
|
|
||||||
| const extendedTextInputProps: Partial<TextInputProps> = useMemo(() => { | ||||||
| return { | ||||||
|
|
@@ -797,12 +797,11 @@ function Panel({ | |||||
| 'anchored', | ||||||
| ) | ||||||
|
|
||||||
| const preventBubbling = | ||||||
| (customOnKeyDown: KeyboardEventHandler<HTMLDivElement> | undefined) => | ||||||
| const preventBubbling = useCallback( | ||||||
| (event: React.KeyboardEvent<HTMLDivElement>) => { | ||||||
| // skip if a TextInput has focus | ||||||
| customOnKeyDown?.(event) | ||||||
| overlayProps?.onKeyDown?.(event as unknown as React.KeyboardEvent<HTMLDivElement>) | ||||||
|
|
||||||
| // skip if a TextInput has focus | ||||||
| const activeElement = document.activeElement as HTMLElement | ||||||
| if (activeElement.tagName === 'INPUT' || activeElement.tagName === 'TEXTAREA') return | ||||||
|
|
||||||
|
|
@@ -817,7 +816,35 @@ function Panel({ | |||||
|
|
||||||
| // if this is a typeahead event, don't propagate outside of menu | ||||||
| event.stopPropagation() | ||||||
| } | ||||||
| }, | ||||||
| [overlayProps], | ||||||
| ) | ||||||
|
|
||||||
| const mergedOverlayProps = useMemo( | ||||||
| () => ({ | ||||||
| role: 'dialog' as const, | ||||||
| 'aria-labelledby': titleId, | ||||||
| 'aria-describedby': subtitle ? subtitleId : undefined, | ||||||
| ...overlayProps, | ||||||
| ...(variant === 'modal' | ||||||
| ? { | ||||||
| top: '50vh' as const, | ||||||
| left: '50vw' as const, | ||||||
| anchorSide: undefined, | ||||||
| } | ||||||
| : {}), | ||||||
| style: { | ||||||
| transform: variant === 'modal' ? 'translate(-50%, -50%)' : undefined, | ||||||
| ...(isKeyboardVisible | ||||||
| ? { | ||||||
| maxHeight: availablePanelHeight !== undefined ? `${availablePanelHeight}px` : 'auto', | ||||||
| } | ||||||
| : {}), | ||||||
| } as React.CSSProperties, | ||||||
| onKeyDown: preventBubbling, | ||||||
| }), | ||||||
| [titleId, subtitle, subtitleId, overlayProps, variant, isKeyboardVisible, availablePanelHeight, preventBubbling], | ||||||
|
||||||
| [titleId, subtitle, subtitleId, overlayProps, variant, isKeyboardVisible, availablePanelHeight, preventBubbling], | |
| [titleId, Boolean(subtitle), subtitleId, overlayProps, variant, isKeyboardVisible, availablePanelHeight, preventBubbling], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
preventBubblingcloses over the entireoverlayPropsobject and lists it in the dependency array. Since onlyoverlayProps.onKeyDownis used, this will recreate the callback whenever any other overlay prop changes (defeating the memoization goal). Consider extractingconst overlayOnKeyDown = overlayProps?.onKeyDownand depending on that instead (and call that inside the handler).This issue also appears on line 801 of the same file.