From 51011bd758e3de2073befe03996f5027d555b76c Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Mon, 4 Aug 2025 13:07:54 -0500 Subject: [PATCH] Clean up FocusScope native impl --- src/components/FocusScope/index.tsx | 54 +++++++++++++++++++---------- 1 file changed, 36 insertions(+), 18 deletions(-) diff --git a/src/components/FocusScope/index.tsx b/src/components/FocusScope/index.tsx index 37538265b2..2f5414905d 100644 --- a/src/components/FocusScope/index.tsx +++ b/src/components/FocusScope/index.tsx @@ -1,20 +1,20 @@ import { Children, cloneElement, - ReactNode, - useRef, - useMemo, + isValidElement, + type ReactElement, + type ReactNode, useCallback, useEffect, - isValidElement, - FunctionComponentElement, + useMemo, + useRef, } from 'react' import { AccessibilityInfo, - Pressable, - View, - Text, findNodeHandle, + Pressable, + Text, + type View, } from 'react-native' import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' @@ -31,10 +31,22 @@ function FocusTrap({children}: {children: ReactNode}) { const {_} = useLingui() const child = useRef(null) - const content = useMemo(() => { + /* + * Here we add a ref to the first child of this component. This currently + * overrides any ref already on that first child, so we throw an error here + * to prevent us from ever accidentally doing this. + */ + const decoratedChildren = useMemo(() => { return Children.toArray(children).map((node, i) => { if (i === 0 && isValidElement(node)) { - return cloneElement(node as FunctionComponentElement, { + const n = node as ReactElement + if (n.props.ref !== undefined) { + throw new Error( + 'FocusScope needs to override the ref on its first child.', + ) + } + return cloneElement(n, { + ...n.props, ref: child, }) } @@ -42,7 +54,7 @@ function FocusTrap({children}: {children: ReactNode}) { }) }, [children]) - const focus = useCallback((ref: View | null) => { + const focusNode = useCallback((ref: View | null) => { if (!ref) return const node = findNodeHandle(ref) if (node) { @@ -52,37 +64,43 @@ function FocusTrap({children}: {children: ReactNode}) { useEffect(() => { setTimeout(() => { - focus(child.current) + focusNode(child.current) }, 1e3) - }, [focus]) + }, [focusNode]) return ( <> { switch (event.nativeEvent.actionName) { case 'activate': { - focus(child.current) + focusNode(child.current) } } }}> - {content} + {decoratedChildren} { switch (event.nativeEvent.actionName) { case 'activate': { - focus(child.current) + focusNode(child.current) } } }}>