Created
May 19, 2021 02:45
-
-
Save Noitidart/406cab0254a488eaa5854f6f950879e2 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { useCallback, useEffect, useRef } from 'react';import { defaults, omit, pick } from 'lodash'; | |
import { useController, UseControllerProps } from 'react-hook-form'; | |
import { TextInput, TextInputProps } from 'react-native';import { HookedInputTransformer } from 'src/lib/react-hook-form-transforms';interface HookedTextInputProps | |
extends Omit<TextInputProps, 'defaultValue'>, | |
UseControllerProps { | |
transform?: HookedInputTransformer; | |
}export default function withHookedInput( | |
ControlledInputComponent: typeof TextInput | |
) { | |
function HookedInput({ transform, ...props }: HookedTextInputProps) { | |
const controllerProps = defaults( | |
pick(props, 'control', 'defaultValue', 'name', 'rules'), | |
{ defaultValue: '' } | |
); const textInputProps = omit(props, Object.keys(controllerProps)); const controller = useController(controllerProps); // the last value that had its equivalent string representation rendered in | |
// the UI | |
const savedLastUiValue = useRef(controllerProps.defaultValue); const handleChangeText = useCallback( | |
(text: Parameters<NonNullable<TextInputProps['onChangeText']>>[0]) => { | |
// Don't use nullish coalescing here because a valid output can be | |
// `undefined` or `null`. | |
const transformedValue = transform?.output | |
? transform?.output(text) | |
: text; savedLastUiValue.current = transformedValue; controller.field.onChange(transformedValue); | |
props.onChangeText?.(text); | |
}, | |
[] | |
); const textInputRef = useRef<TextInput>(); // Set value of TextInput if value is changed externally (not due to | |
// TextInput.onChangeText) | |
useEffect(() => { | |
// Use `includes` because value may be `NaN` and `NaN === NaN` is `false`, | |
// but `[NaN].includes(NaN)` is `true`. | |
const isValueRenderedInUi = [savedLastUiValue.current].includes( | |
controller.field.value | |
); if (isValueRenderedInUi === false) { | |
// Untransform the value. Meaning get the string representation of the value. | |
const text = | |
transform?.input?.(controller.field.value) ?? | |
String(controller.field.value); // Update savedLastUiValue because doing setNativeProps does not trigger | |
// onChange (at least on ios, need to verify on android) | |
savedLastUiValue.current = controller.field.value; textInputRef.current?.setNativeProps({ text }); | |
} | |
}, [controller.field.value]); const handleBlur = React.useCallback( | |
(e: Parameters<NonNullable<TextInputProps['onBlur']>>[0]) => { | |
controller.field.onBlur(); | |
props.onBlur?.(e); | |
}, | |
[] | |
); // Memo TextInput so it does not re-render when `controller` re-renders due to | |
// `value` change | |
return ( | |
<MemoedInputComponent | |
{...textInputProps} | |
ref={textInputRef} | |
onChangeText={handleChangeText} | |
onBlur={handleBlur} | |
defaultValue={controllerProps.defaultValue} | |
/> | |
); | |
} // do not accept defaultValue or value props, as HookedTextInput will use | |
const MemoedInputComponent = React.memo( | |
React.forwardRef<TextInput, Omit<TextInputProps, 'value'>>((props, ref) => ( | |
<ControlledInputComponent {...props} ref={ref} /> | |
)) | |
); return HookedInput; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment