Skip to content

Instantly share code, notes, and snippets.

@Noitidart
Created May 12, 2021 07:14
Show Gist options
  • Save Noitidart/fcb79f97e86a923f285b1448de85fdd1 to your computer and use it in GitHub Desktop.
Save Noitidart/fcb79f97e86a923f285b1448de85fdd1 to your computer and use it in GitHub Desktop.
Non-re-rendering react-hook-form TextInput component for react-native.
import * as React from 'react';
import { Text, View, StyleSheet, TextInput, Button, Alert } from 'react-native';
import { useForm, useController } from 'react-hook-form';
import Constants from 'expo-constants';
import { pick, omit, defaults } from 'lodash';
const REQUIRED = { required: true };
function ClassicHookedTextInput(props) {
const controllerProps = defaults(
pick(props, 'control', 'defaultValue', 'name', 'rules'),
{ defaultValue: '' }
);
const textInputProps = omit(props, Object.keys(controllerProps));
const controller = useController(controllerProps);
return (
<>
{useRenderCounter('Classic')}
<TextInput
{...textInputProps}
onChangeText={controller.field.onChange}
onBlur={controller.field.onBlur}
/>
</>
);
}
function DirectHookedTextInput(props) {
const controllerProps = defaults(
pick(props, 'control', 'defaultValue', 'name', 'rules'),
{ defaultValue: '' }
);
const textInputProps = omit(props, Object.keys(controllerProps));
const controller = useController(controllerProps);
const lastChangedText = React.useRef(controllerProps.defaultValue);
const handleChangeText = React.useCallback((text) => {
lastChangedText.current = text;
controller.field.onChange(text);
}, []);
const textInputRef = React.useRef();
// Set value of TextInput if value is changed externally (not due to TextInput.onChangeText)
React.useEffect(() => {
console.log(
`controller.field.value changed to: "${controller.field.value}". lastChangedText is: "${lastChangedText.current}".`
);
if (lastChangedText.current !== controller.field.value) {
// Doing setNativeProps does not trigger TextInput.onChangeText, so set it here
lastChangedText.current = controller.field.value;
textInputRef.current &&
textInputRef.current.setNativeProps({ text: controller.field.value });
}
}, [controller.field.value]);
const handleBlur = React.useCallback((e) => {
controller.field.onBlur(e);
}, []);
return (
<>
{useRenderCounter('DirectController')}
<DirectTextInput
{...textInputProps}
ref={textInputRef}
onChangeText={handleChangeText}
onBlur={handleBlur}
/>
</>
);
}
const DirectTextInput = React.memo(
React.forwardRef((props, ref) => (
<>
{useRenderCounter('Direct')}
<TextInput {...props} ref={ref} />
</>
))
);
function useRenderCounter(label = '') {
const inputRef = React.useRef();
const savedCount = React.useRef(0);
React.useEffect(() => {
savedCount.current++;
inputRef.current?.setNativeProps({
text: label + (label ? ': ' : '') + savedCount.current.toString(),
});
});
return (
<TextInput
style={{
alignSelf: 'flex-start',
backgroundColor: '#ccc',
borderRadius: 4,
paddingHorizontal: 4,
paddingVertical: 2,
marginHorizontal: 6,
fontSize: 12,
// position: 'absolute',
transform: [{ translateX: -4 }, { translateY: -4 }],
}}
pointerEvents="none"
defaultValue={savedCount.current.toString()}
ref={inputRef}
/>
);
}
export default () => {
const form = useForm();
const handleSubmit = (data) => console.log(data);
return (
<View style={styles.container}>
<Text style={styles.label}>First name</Text>
<ClassicHookedTextInput
control={form.control}
name="firstName"
rules={REQUIRED}
style={styles.input}
/>
<Text style={styles.label}>Last name</Text>
<DirectHookedTextInput
control={form.control}
name="lastName"
rules={REQUIRED}
style={styles.input}
/>
<View style={styles.button}>
<Button
style={styles.buttonInner}
color
title="Reset"
onPress={() => {
form.reset({
firstName: 'Bill',
lastName: 'Luo',
});
}}
/>
</View>
<View style={styles.button}>
<Button
style={styles.buttonInner}
color
title="Button"
onPress={form.handleSubmit(handleSubmit)}
/>
</View>
</View>
);
};
const styles = StyleSheet.create({
label: {
color: 'white',
margin: 20,
marginLeft: 0,
},
button: {
marginTop: 40,
color: 'white',
height: 40,
backgroundColor: '#ec5990',
borderRadius: 4,
},
container: {
flex: 1,
justifyContent: 'center',
paddingTop: Constants.statusBarHeight,
padding: 8,
backgroundColor: '#0e101c',
},
input: {
backgroundColor: 'white',
borderColor: 'none',
height: 40,
padding: 10,
borderRadius: 4,
},
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment