diff --git a/UNRELEASED.md b/UNRELEASED.md index c989f7613b0..81c1222cc56 100644 --- a/UNRELEASED.md +++ b/UNRELEASED.md @@ -6,6 +6,8 @@ Use [the changelog guidelines](https://git.io/polaris-changelog-guidelines) to f ### Enhancements +- Prevented `KeypressListener` attaching/detaching on every render ([#4173](https://github.com/Shopify/polaris-react/pull/4173)) + ### Bug fixes ### Documentation diff --git a/src/components/Autocomplete/components/ComboBox/ComboBox.tsx b/src/components/Autocomplete/components/ComboBox/ComboBox.tsx index 789de094b52..0de83583d7f 100644 --- a/src/components/Autocomplete/components/ComboBox/ComboBox.tsx +++ b/src/components/Autocomplete/components/ComboBox/ComboBox.tsx @@ -1,4 +1,4 @@ -import React, {useState, useEffect, useLayoutEffect, useCallback} from 'react'; +import React, {useState, useEffect, useCallback} from 'react'; import {useUniqueId} from '../../../../utilities/unique-id'; import {useToggle} from '../../../../utilities/use-toggle'; @@ -8,7 +8,7 @@ import {Popover, PopoverProps} from '../../../Popover'; import {ActionListItemDescriptor, Key} from '../../../../types'; import {KeypressListener} from '../../../KeypressListener'; import {EventListener} from '../../../EventListener'; -import {isServer} from '../../../../utilities/target'; +import {useIsomorphicLayoutEffect} from '../../../../utilities/use-isomorphic-layout-effect'; import {ComboBoxContext} from './context'; import styles from './ComboBox.scss'; @@ -71,8 +71,6 @@ export function ComboBox({ setFalse: forcePopoverActiveFalse, } = useToggle(false); - const useIsomorphicLayoutEffect = isServer ? useEffect : useLayoutEffect; - const id = useUniqueId('ComboBox', idProp); const getActionsWithIds = useCallback( diff --git a/src/components/KeypressListener/KeypressListener.tsx b/src/components/KeypressListener/KeypressListener.tsx index 367addc659e..6930a030d22 100644 --- a/src/components/KeypressListener/KeypressListener.tsx +++ b/src/components/KeypressListener/KeypressListener.tsx @@ -1,5 +1,6 @@ -import {useEffect} from 'react'; +import {useCallback, useEffect, useRef} from 'react'; +import {useIsomorphicLayoutEffect} from '../../utilities/use-isomorphic-layout-effect'; import type {Key} from '../../types'; export interface KeypressListenerProps { @@ -15,18 +16,25 @@ export function KeypressListener({ handler, keyEvent = 'keyup', }: KeypressListenerProps) { - const handleKeyEvent = (event: KeyboardEvent) => { + const tracked = useRef({handler, keyCode}); + + useIsomorphicLayoutEffect(() => { + tracked.current = {handler, keyCode}; + }, [handler, keyCode]); + + const handleKeyEvent = useCallback((event: KeyboardEvent) => { + const {handler, keyCode} = tracked.current; if (event.keyCode === keyCode) { handler(event); } - }; + }, []); useEffect(() => { document.addEventListener(keyEvent, handleKeyEvent); return () => { document.removeEventListener(keyEvent, handleKeyEvent); }; - }); + }, [keyEvent, handleKeyEvent]); return null; } diff --git a/src/utilities/use-isomorphic-layout-effect.ts b/src/utilities/use-isomorphic-layout-effect.ts new file mode 100644 index 00000000000..1e3931cf3d7 --- /dev/null +++ b/src/utilities/use-isomorphic-layout-effect.ts @@ -0,0 +1,5 @@ +import {useEffect, useLayoutEffect} from 'react'; + +import {isServer} from './target'; + +export const useIsomorphicLayoutEffect = isServer ? useEffect : useLayoutEffect;