diff --git a/src/view/use-drag-handle/use-focus-retainer.js b/src/view/use-drag-handle/use-focus-retainer.js index cb02823f4a..bae213d4c9 100644 --- a/src/view/use-drag-handle/use-focus-retainer.js +++ b/src/view/use-drag-handle/use-focus-retainer.js @@ -27,6 +27,9 @@ export default function useFocusRetainer(args: Args): Result { isFocusedRef.current = false; }, []); + // This effect handles: + // - giving focus on mount + // - registering focus on unmount useIsomorphicLayoutEffect(() => { // mounting: try to restore focus const first: Args = lastArgsRef.current; @@ -63,9 +66,19 @@ export default function useFocusRetainer(args: Args): Result { }; }, [getDraggableRef]); - const lastDraggableRef = useRef(getDraggableRef()); + // will always be null on the first render as nothing has mounted yet + const lastDraggableRef = useRef(null); + // This effect restores focus to an element when a + // ref changes while a component is still mounted. + // This can happen when a drag handle is moved into a portal useIsomorphicLayoutEffect(() => { + // this can happen on the first mount - no draggable ref is set + // this effect is not handling initial mounting + if (!lastDraggableRef.current) { + return; + } + const draggableRef: ?HTMLElement = getDraggableRef(); // Cannot focus on nothing diff --git a/test/unit/view/drag-handle/focus-management.spec.js b/test/unit/view/drag-handle/focus-management.spec.js index 865ef2aafc..b1dacbce37 100644 --- a/test/unit/view/drag-handle/focus-management.spec.js +++ b/test/unit/view/drag-handle/focus-management.spec.js @@ -2,6 +2,7 @@ import React, { type Node } from 'react'; import invariant from 'tiny-invariant'; import ReactDOM from 'react-dom'; +import { Simulate } from 'react-dom/test-utils'; import { mount } from 'enzyme'; import type { ReactWrapper } from 'enzyme'; import type { DragHandleProps } from '../../../../src/view/use-drag-handle/drag-handle-types'; @@ -12,6 +13,7 @@ import forceUpdate from '../../../utils/force-update'; import AppContext, { type AppContextValue, } from '../../../../src/view/context/app-context'; +import createRef from '../../../utils/create-ref'; const body: ?HTMLElement = document.body; invariant(body, 'Cannot find body'); @@ -385,4 +387,53 @@ describe('Focus retention moving between lists (focus retention between mounts)' // focus maintained on button expect(button).toBe(document.activeElement); }); + + it('should not steal focus from an element with auto focus on mount', () => { + function App() { + const ref = createRef(); + return ( + + true} + > + {(dragHandleProps: ?DragHandleProps) => ( +
+ Drag me! + {/* autoFocus attribute does give focus, but does not trigger onFocus callback */} +