React Native course ยท Module 5: Forms

React Hook Form - Professional Forms

2 min read ยท
In this lesson4

React Hook Form is the most popular form library - efficient and easy to use!

Installation

npx expo install react-hook-form

Basic 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. 1. What does the useForm hook from react-hook-form return?

  2. 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.

Useful articles