Using React Hook Form with react-native - Part I (set up & validation)

sankhadeeproy007

Sankhadeep Roy

Posted on September 24, 2020

Using React Hook Form with react-native - Part I (set up & validation)

Forms in react have always been a sore point. I personally have tried a lot of solutions (redux-form, lifting state up etc), but never really enjoyed working with them. Thankfully things are a lot better now with Formik and React Hook Form.

There are quite a few examples/tutorials of React Hook Form (to be called as RHF) with react for web, so in this post, we'll learn how to set up and use RHF with react-native forms.

Let us start by creating a react-native app and installing the dependencies (I'll be using Expo, feel free to use react-native init).

expo init form-example
Enter fullscreen mode Exit fullscreen mode
cd form-example && yarn add react-hook-form react-native-tailwindcss
Enter fullscreen mode Exit fullscreen mode

We'll now build a basic form with two inputs, name and email. Let's create the two components that we will use in this example. In the project root, create a folder called components. Create 2 files called Button.js and Input.js.

Button.js
// Button.js

import React from 'react';
import { TouchableOpacity, Text } from 'react-native';
import { t } from 'react-native-tailwindcss';

export default function Button({ label, ...props }) {
  return (
    <TouchableOpacity activeOpacity={0.8} {...props} style={styles.button}>
      <Text style={styles.buttonLabel}>{label}</Text>
    </TouchableOpacity>
  );
}

const styles = {
  button: [t.selfStretch, t.bgGreen600, t.itemsCenter, t.pY3, t.rounded],
  buttonLabel: [t.textWhite, t.textLg]
};

Enter fullscreen mode Exit fullscreen mode
Input.js
// Input.js

import React from 'react';
import { View, Text, TextInput } from 'react-native';
import { t } from 'react-native-tailwindcss';

export default function Input(props) {
  return (
    <View style={styles.wrapper}>
      <TextInput
        style={[styles.input, props.error && t.borderRed500, props.style]}
        {...props}
      />
      {props.errorText && (
        <Text style={styles.errorText}>{props.errorText}</Text>
      )}
    </View>
  );
}

const styles = {
  wrapper: [t.selfStretch, t.mB5],
  input: [
    t.h11,
    t.border,
    t.selfStretch,
    t.p2,
    t.borderGray500,
    t.rounded,
    t.textBase,
    t.textGray700
  ],
  errorText: [t.mT1, t.textRed500]
};

Enter fullscreen mode Exit fullscreen mode

Let us now replace the contents of the App.js file with the following

//  App.js

import React, { useState } from 'react';
import { StyleSheet, Switch, Text, View } from 'react-native';
import { t, color } from 'react-native-tailwindcss';

import Input from './components/Input';
import Button from './components/Button';

export default function App() {
  const [isBillingDifferent, setIsBillingDifferent] = useState(false);

  const toggleBilling = () => {
    setIsBillingDifferent((prev) => !prev);
  };

  return (
    <View style={styles.container}>
      <Input placeholder="Name" />
      <Input placeholder="Email" />
      <View style={styles.switch}>
        <Text style={styles.switchText}>Billing different</Text>
        <Switch
          trackColor={{ false: color.gray200, true: color.green600 }}
          thumbColor={color.gray100}
          ios_backgroundColor={color.gray800}
          onValueChange={toggleBilling}
          value={isBillingDifferent}
        />
      </View>
      {isBillingDifferent && (
        <>
          <Input placeholder="Billing name" />
          <Input placeholder="Billing email" />
        </>
      )}
      <Button label="Submit" />
    </View>
  );
}

const styles = {
  container: [t.flex1, t.justifyCenter, t.itemsCenter, t.p6, t.bgGray200],
  switch: [t.mB4, t.selfStart, t.flexRow, t.itemsCenter],
  switchText: [t.textBase, t.mR3, t.textGray800]
};

Enter fullscreen mode Exit fullscreen mode

Now when we run our app, we should see something like this, note that we have a switch which toggles between showing 2 extra fields (we'll use them in Part II of this article).

Simulator Screen Shot - iPhone 11 - 2020-09-16 at 20.55.39

So we have got our basic UI setup done, let us now add RHF to our app. Add the following line below your last import

import { useForm, Controller } from 'react-hook-form';
Enter fullscreen mode Exit fullscreen mode

We now use the useForm hook (inside our component) to get the handleSubmit and control values.

// export default function App() {
const { handleSubmit, control } = useForm();
Enter fullscreen mode Exit fullscreen mode

Using RHF with react-native is a bit different than react for web. With react, we can register an input through its ref (or inputRef in case of some component libraries).
However, in the case of react-native, we need to use the Controller component and the render our Input inside a renderProp. We also need to give it a name and pass it a control prop. Let's change our code accordingly and see how it looks like

<Controller
    name="name"
    control={control}
    render={({ onChange, value }) => (
        <Input
          onChangeText={(text) => onChange(text)}
          value={value}
          placeholder="Name"
        />
    )}
  />
Enter fullscreen mode Exit fullscreen mode

We do the same for our Email field and replace by the name and placeholder props accordingly.

At this point when we run our app, we'll probably get a warning prompting us to add a defaultValue for our fields. Let us add the defaultValues for the fields

//<Controller
    defaultValue=""
//  name="name"

//<Controller
    defaultValue=""
//  name="email"
Enter fullscreen mode Exit fullscreen mode

So, now that we have wired up our form with RHF, let's log these values on press of the Submit button. To do so, we need to wire up handleSubmit (from the useForm hook) to the onPress of our button. Inside handleSubmit we pass our onSubmit function.
In the onSubmit function, we will log the values entered.

<Button onPress={handleSubmit(onSubmit)} label="Submit" />

// onSubmit method
const onSubmit = (data) => {
  console.log(data, 'data');
};
Enter fullscreen mode Exit fullscreen mode

Now when we enter some values and press the button, we should see something like this in our logs.

Screen Shot 2020-09-19 at 1.15.58 PM

So far so good! Let's add some validation to our fields and notify the user when the fields are not filled.
First, we need to add rules our field controllers and then we will use the errors object from the useForm hook to check for any errors in our form.

// export default function App() {
const { handleSubmit, control, errors } = useForm();

// name controller
// control={control}
rules={{
    required: { value: true, message: 'Name is required' }
  }}

// email controller
// control={control}
rules={{
    required: { value: true, message: 'Email is required' }
  }}

Enter fullscreen mode Exit fullscreen mode

Note that we can also use rules={{required: true}} and set the error message separately. Let us now add the error and errorText props to our Input component.

// name input
<Input
    error={errors.name}
    errorText={errors?.name?.message}
 // onChangeText={(text) => onChange(text)}


// email input
<Input
    error={errors.email}
    errorText={errors?.email?.message}
 // onChangeText={(text) => onChange(text)}
Enter fullscreen mode Exit fullscreen mode

Well done! If we now press the submit button without filling the fields, we should see something like this

Simulator Screen Shot - iPhone 11 - 2020-09-24 at 20.51.59

One last thing! Let us also add a check that only allows valid email ids to be submitted. So we add another rule to our email field called pattern.
The name itself is pretty self explanatory, so we'll need an email regex to validate our input with. (I totally didn't copy the regex from here!)

// After the last import statement
const EMAIL_REGEX = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;

// email controller
// required: { value: true, message: 'Email is required' },
   pattern: {
     value: EMAIL_REGEX,
     message: 'Not a valid email'
   }
Enter fullscreen mode Exit fullscreen mode

Great! Now we have successfully added email validation to our form.

Simulator Screen Shot - iPhone 11 - 2020-09-24 at 21.11.31

In the next part, we will learn how to populate our input fields with data from backend API and edit it. We'll also take a look at how to do conditional fields (fields based on user input).

Thanks for reading and do give it a ❤️ if you found it useful!
Happy coding!

💖 💪 🙅 🚩
sankhadeeproy007
Sankhadeep Roy

Posted on September 24, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related