import React from 'react'; import { findNodeHandle } from 'react-native'; import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view'; import { TextArea } from '../components'; type ScrollToInput = KeyboardAwareScrollView['scrollToFocusedInput']; /** * KASV is an abbreviation of `KeyboardAwareScrollView`. This type is used to * add type support to the ref returned by the component, with which we are doing * something non-standard as a workaround: accessing the internal properties of the * component. */ type KASVRef = JSX.Element & { getScrollResponder: () => { props: { scrollToFocusedInput: ScrollToInput; }; }; }; type TextAreaFocusHandler = React.ComponentProps<typeof TextArea>['onFocus']; /** * `useKASVWorkaround` (KASV -> KeyboardAwareScrollView) is a reusable hook to * implement a (hacky) workaround [discussed with the maintainer](https://github.com/APSL/react-native-keyboard-aware-scroll-view/issues/451) * for a bug we were encountering after upgrading from Expo 38 to Expo 42 in July of 2021. * * For brief context, the app would crash with an undefined error after attempting to focus * a TextArea input that was interacting with KeyboardAwareScrollView. This was due to us * attempting to invoke `scrollToFocusedInput` in way recommended by the docs. * * The hook provides `props`, which are meant to be spread onto the * `KeyboardAwareScrollView`. This will allow the `innerRef` prop to be set correctly. * * The hook then also provides `scrollToFocusedInput`, which is the function we were * originally calling with a ref workaround. * */ export function useKASVWorkaround() { const scrollRef = React.useRef<JSX.Element>(); const scrollToInputRef = React.useRef<ScrollToInput>(); function innerRef(ref: JSX.Element) { scrollRef.current = ref; if (!ref) { return; } const refWithWorkAround = (ref as unknown) as KASVRef; const { getScrollResponder } = refWithWorkAround; if (!getScrollResponder) { return; } const responder = getScrollResponder(); if (!responder) { return; } const { props } = responder; if (!props) { return; } const { scrollToFocusedInput } = props; if (!scrollToFocusedInput) { return; } scrollToInputRef.current = scrollToFocusedInput; } const scrollToFocusedInput: TextAreaFocusHandler = ({ target }) => { const { current } = scrollToInputRef; if (!current) { return; } const scrollToFocusedInput = current; if (!scrollToFocusedInput) { return; } const handle = findNodeHandle(target); if (handle == null) { return; } scrollToFocusedInput(handle); }; return { scrollToFocusedInput, props: { innerRef, }, }; }