Skip to content

Instantly share code, notes, and snippets.

@Noitidart
Created May 19, 2021 02:45
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Noitidart/406cab0254a488eaa5854f6f950879e2 to your computer and use it in GitHub Desktop.
Save Noitidart/406cab0254a488eaa5854f6f950879e2 to your computer and use it in GitHub Desktop.
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