React Native course ยท Module 5: Forms
React Hook Form - Professional Forms
In this lesson4
React Hook Form is the most popular form library - efficient and easy to use!
Installation
npx expo install react-hook-formBasic Form
import { useForm, Controller } from 'react-hook-form';
interface FormData {
name: string;
email: string;
password: string;
}
function RegisterForm() {
const {
control,
handleSubmit,
formState: { errors, isSubmitting },
} = useForm<FormData>({
defaultValues: {
name: '',
email: '',
password: '',
},
});
const onSubmit = async (data: FormData) => {
console.log('Form data:', data);
// Send to server
};
return (
<View style={styles.container}>
<Controller
control={control}
name="name"
rules={{
required: 'Name is required',
minLength: { value: 2, message: 'Min. 2 characters' },
}}
render={({ field: { onChange, onBlur, value } }) => (
<TextInput
style={[styles.input, errors.name && styles.inputError]}
placeholder="Name"
onBlur={onBlur}
onChangeText={onChange}
value={value}
/>
)}
/>
{errors.name && (
<Text style={styles.error}>{errors.name.message}</Text>
)}
<Controller
control={control}
name="email"
rules={{
required: 'Email is required',
pattern: {
value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i,
message: 'Invalid email',
},
}}
render={({ field: { onChange, onBlur, value } }) => (
<TextInput
style={[styles.input, errors.email && styles.inputError]}
placeholder="Email"
keyboardType="email-address"
autoCapitalize="none"
onBlur={onBlur}
onChangeText={onChange}
value={value}
/>
)}
/>
{errors.email && (
<Text style={styles.error}>{errors.email.message}</Text>
)}
<Pressable
style={styles.button}
onPress={handleSubmit(onSubmit)}
disabled={isSubmitting}
>
<Text style={styles.buttonText}>
{isSubmitting ? 'Sending...' : 'Register'}
</Text>
</Pressable>
</View>
);
}Custom Input Component
interface FormInputProps {
control: any;
name: string;
rules?: object;
placeholder?: string;
secureTextEntry?: boolean;
keyboardType?: KeyboardTypeOptions;
}
function FormInput({
control,
name,
rules,
placeholder,
secureTextEntry,
keyboardType,
}: FormInputProps) {
return (
<Controller
control={control}
name={name}
rules={rules}
render={({
field: { onChange, onBlur, value },
fieldState: { error },
}) => (
<View style={styles.inputContainer}>
<TextInput
style={[styles.input, error && styles.inputError]}
placeholder={placeholder}
onBlur={onBlur}
onChangeText={onChange}
value={value}
secureTextEntry={secureTextEntry}
keyboardType={keyboardType}
/>
{error && <Text style={styles.error}>{error.message}</Text>}
</View>
)}
/>
);
}
// Usage
function CleanForm() {
const { control, handleSubmit } = useForm();
return (
<View>
<FormInput
control={control}
name="name"
placeholder="Name"
rules={{ required: 'Name required' }}
/>
<FormInput
control={control}
name="email"
placeholder="Email"
keyboardType="email-address"
rules={{
required: 'Email required',
pattern: { value: /\S+@\S+\.\S+/, message: 'Invalid email' },
}}
/>
</View>
);
}Watch and setValue
function DynamicForm() {
const { control, watch, setValue } = useForm({
defaultValues: { country: '', city: '' },
});
const country = watch('country');
useEffect(() => {
// Reset city when country changes
setValue('city', '');
}, [country]);
return (
<View>
<FormInput control={control} name="country" placeholder="Country" />
<FormInput control={control} name="city" placeholder="City" />
<Text>Selected country: {country}</Text>
</View>
);
}React Hook Form minimizes re-renders and offers great TypeScript support!
Code for this lesson: App.tsx
import React from 'react';
import { View, Text, TextInput, Pressable, StyleSheet } from 'react-native';
import { useForm, Controller } from 'react-hook-form';
interface FormData {
email: string;
}
export default function App() {
// TODO: get control, handleSubmit and formState: { errors } from useForm<FormData>()
// pass defaultValues: { email: '' }
const onSubmit = (data: FormData) => {
console.log('Form data:', data);
};
return (
<View style={styles.container}>
{/* TODO: add a <Controller> that:
- takes control={control}
- has name="email"
- has rules={{ required: 'Email is required' }}
- in render returns a <TextInput> with onChangeText={onChange}, onBlur={onBlur}, value={value} */}
{/* TODO: below the field show the error: errors.email && <Text style={styles.error}>{errors.email.message}</Text> */}
<Pressable style={styles.button} onPress={handleSubmit(onSubmit)}>
<Text style={styles.buttonText}>Submit</Text>
</Pressable>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 20,
justifyContent: 'center',
backgroundColor: '#1a5276',
},
input: {
backgroundColor: '#fff',
borderRadius: 8,
padding: 14,
fontSize: 16,
color: '#1a5276',
marginBottom: 8,
},
error: {
color: '#e74c3c',
marginBottom: 12,
},
button: {
backgroundColor: '#2980b9',
borderRadius: 8,
padding: 14,
alignItems: 'center',
},
buttonText: {
color: '#fff',
fontSize: 16,
fontWeight: '600',
},
});Docs entries used in this lesson
Spotted a mistake in this lesson?
Check yourself
Answer the questions from this lesson. Pick an answer to see right away whether it is correct.
1. What does the useForm hook from react-hook-form return?
2. How do you add 'required' validation in Controller?
Hands-on tasks in the game
- Vertical ordering
Arrange the Controller elements from outer to inner:
- Code editor
Complete the code with useForm and Controller.