diff --git a/src/components/forms/SearchInput.tsx b/src/components/forms/SearchInput.tsx index d77b62de9d..c4a77069c7 100644 --- a/src/components/forms/SearchInput.tsx +++ b/src/components/forms/SearchInput.tsx @@ -47,7 +47,13 @@ export function SearchInput({ mergeRefs([internalRef, ref])(node)} label={label || l`Search`} value={value} placeholder={l`Search`} diff --git a/src/components/forms/TextField.tsx b/src/components/forms/TextField.tsx index 936a8a1bec..9c077f8342 100644 --- a/src/components/forms/TextField.tsx +++ b/src/components/forms/TextField.tsx @@ -26,6 +26,7 @@ import { import {useInteractionState} from '#/components/hooks/useInteractionState' import {type Props as SVGIconProps} from '#/components/icons/common' import {Text} from '#/components/Typography' +import {IS_WEB} from '#/env' const Context = createContext<{ inputRef: React.RefObject | null> | null @@ -101,11 +102,13 @@ export function Root({children, isInvalid = false, style}: RootProps) { {zIndex: 0}, style, ]} - {...web({ - onClick: () => inputRef.current?.focus(), - onMouseOver: onHoverIn, - onMouseOut: onHoverOut, - })}> + {...(IS_WEB + ? { + onClick: () => inputRef.current?.focus(), + onMouseOver: onHoverIn, + onMouseOut: onHoverOut, + } + : {})}> {children} diff --git a/src/lib/hooks/useDraggableScrollView.ts b/src/lib/hooks/useDraggableScrollView.ts index a8b8d0a9a2..8008b52713 100644 --- a/src/lib/hooks/useDraggableScrollView.ts +++ b/src/lib/hooks/useDraggableScrollView.ts @@ -74,8 +74,13 @@ export function useDraggableScroll< } }, [cursor]) + /* + * Deferred into the callback so the merge does not happen during the render of + * whichever component uses this hook - see SearchInput for the full reasoning. + */ const refs = useMemo( - () => mergeRefs(outerRef ? [ref, outerRef] : [ref]), + () => (node: Scrollable | null) => + mergeRefs(outerRef ? [ref, outerRef] : [ref])(node), [ref, outerRef], ) diff --git a/src/lib/merge-refs.ts b/src/lib/merge-refs.ts index d2b187e1e9..8e92298f7a 100644 --- a/src/lib/merge-refs.ts +++ b/src/lib/merge-refs.ts @@ -31,7 +31,7 @@ function assignRef( * @returns The function `mergeRefs` is being returned. It takes an array of mutable or legacy refs and * returns a ref callback function that can be used to merge multiple refs into a single ref. */ -export function mergeRefs(refs: (Ref | undefined)[]): Ref { +export function mergeRefs(refs: (Ref | undefined)[]): RefCallback { return (value: T | null) => { const cleanups: (() => void)[] = [] diff --git a/src/screens/Onboarding/StepFinished/ValuePropositionPager.tsx b/src/screens/Onboarding/StepFinished/ValuePropositionPager.tsx index 93aa6acc2b..23f2931bd6 100644 --- a/src/screens/Onboarding/StepFinished/ValuePropositionPager.tsx +++ b/src/screens/Onboarding/StepFinished/ValuePropositionPager.tsx @@ -1,4 +1,4 @@ -import {useRef, useState} from 'react' +import {useEffect, useRef, useState} from 'react' import {View} from 'react-native' import PagerView from 'react-native-pager-view' import {type PagerViewOnPageSelectedEvent} from 'react-native-pager-view' @@ -26,9 +26,17 @@ export function ValuePropositionPager({ if (step !== activePage) { setActivePage(step) - ref.current?.setPage(step) } + /* + * In an effect rather than inline above: driving the pager is a side effect, + * and reading a ref during render is a Rules of React violation. `initialPage` + * already covers the first render, so the mount run is a no-op. + */ + useEffect(() => { + ref.current?.setPage(step) + }, [step]) + const images = [PROP_1[t.name], PROP_2[t.name], PROP_3[t.name]] return (