Using React Hook Form with react-native - Part I (set up & validation)
Tags: reactnative, javascript, tutorial, react
Published: 2020-09-24 16:10:18

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](https://formik.org/) and [React Hook Form](https://react-hook-form.com/).

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

```bash
expo init form-example
```

```bash
cd form-example && yarn add react-hook-form react-native-tailwindcss
```

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

```javascript
// 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]
};

```

###### Input.js

```javascript
// 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]
};

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

```javascript
//  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]
};

```

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](https://dev-to-uploads.s3.amazonaws.com/i/xgbs29bleje1eutjxxkp.png)



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

```javascript
import { useForm, Controller } from 'react-hook-form';
```

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

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

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

```javascript
<Controller
    name="name"
    control={control}
    render={({ onChange, value }) => (
        <Input
          onChangeText={(text) => onChange(text)}
          value={value}
          placeholder="Name"
        />
    )}
  />
```

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 

```javascript
//<Controller
    defaultValue=""
//  name="name"

//<Controller
    defaultValue=""
//  name="email"
```
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.

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

// onSubmit method
const onSubmit = (data) => {
  console.log(data, 'data');
};
```

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](https://dev-to-uploads.s3.amazonaws.com/i/twu564qronrkxa5seg0k.png)

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.

```javascript
// 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' }
  }}

```

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.

```react
// 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)}
```

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](https://dev-to-uploads.s3.amazonaws.com/i/mfyh7fd79u6zvvs9znxs.png)

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](https://stackoverflow.com/a/46181/2594826)!)

```javascript
// 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'
   }
```

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

![Simulator Screen Shot - iPhone 11 - 2020-09-24 at 21.11.31](https://dev-to-uploads.s3.amazonaws.com/i/sizsnk09ugdjkt7sra7m.png)
 

In the [next part](https://dev.to/sankhadeeproy007/using-react-hook-form-with-react-native-part-ii-pre-filled-values-conditional-fields-ik1), 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!