Building a Form using React Native and Animated API with Unit testing
Tags: react, reactnative, tutorial, webdev
Published: 2023-01-16 11:37:20

**React Native** is a cross-platform mobile development framework based on JavaScript and Node.js. It allows you to build mobile applications for **Android** and **iOS** without writing code for each platform.

In this article, I'm going to walk you through building a simple form using **Formik**, perform some basic **validations** and **unit testing**, and then add a little bit of animations using the **Animated API**.

We are going to use **Expo** to set up our development environment, so let's first install **Expo CLI** globally.

`npm install -g expo-cli`

Now let's create an Expo project

`npx create-expo-app react-native-formik`

Once the installation finishes, you can run the app using the following command

`expo start`

You can view the app using the **Xcode** or Android Studio **simulator**, or scan the **QR code** shown on your terminal after you download the **Expo Go app** from the App Store or Google play, to be able to run the app on your mobile phone.

Let's now do some coding!

We will start by installing **Formik**

`npm install formik --save`

**Formik** is a lightweight form library for **ReactJS** and **React Native**. It provides you with a reusable form component which reduces a lot of boilerplate code and simplifies handling the three most annoying parts in the form building process:

1. Getting values in and out of form state 
2. Validation and error messages 
3. Handling form submission

Copy the code below into `App.js`

```
import React from "react";
import {
  TextInput,
  Pressable,
  Text,
  View,
  StyleSheet
} from "react-native";
import { Formik } from "formik";

export default function App() {
  return (
    <View style={styles.container}>
      <Formik
        initialValues={{ username: "", email: "" }}
        onSubmit={(values) => console.log(values)}
      >
        {({
          handleSubmit,
          handleChange,
          values
        }) => {
          return (
            <View>
              <TextInput
                onChangeText={handleChange("username")}
                value={values.username}
                placeholder="username"
                style={styles.input}
              />

              <TextInput
                onChangeText={handleChange("email")}
                value={values.email}
                placeholder="email"
                style={styles.input}
              />

              <View>
                <Pressable
                  style={[styles.pressable, { transform: [{ scale }] }]}
                  onPress={handleSubmit}
                  onPressIn={onPressIn}
                  testID="button"
                  onPressOut={onPressOut}
                >
                  <Text style={styles.buttonText}>submit</Text>
                </Pressable>
              </View>
            </View>
          );
        }}
      </Formik>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: "#fff",
    alignItems: "center",
    justifyContent: "center",
  },
  input: {
    width: 250,
    height: 40,
    marginBottom: 5,
    borderWidth: 2,
    padding: 5,
    borderColor: "#000",
  },
  pressable: {
    width: 250,
    height: 40,
    alignItems: "center",
    justifyContent: "center",
    backgroundColor: "#000",
  },
  text: {
    color: "#000",
  },
  buttonText: {
    color: "#fff",
  },
});
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x4tnc827af1w37ljmg0f.png)
Here we are setting the Formik component

Notice that **React Native** does not provide a form component, so we are setting Formik and inside of it we are importing View as a wrapper for our form components, you can think of **View** as the equivalent of **div** in web development.

**Button** component supports a minimal level of customization and has no style property, so we are using **Pressable** as an alternative to customize our own button.

**handleSubmit** is auto generated from Formik to handle validation and submission.

We provide the Formik component with the **initialValues** object of our form. Here we are just assigning username and email.

Then we pass the **handleChange** method provided by Formik into the **onChangeText** callback in the **TextField** components with their corresponding key in the initialValues object.

And finally, we pass **onSubmit** as a prop into the Formik component, and here we are just printing the values to the console.

## Validation:

Formik comes with built-in support for **Yup** which is a JavaScript schema builder for value parsing and validation.

Let's install Yup

`npm install yup --save`

Now we import Yup and create our validation schema

```
...

import * as Yup from "yup";

const Schema = Yup.object().shape({
  username: Yup.string().required("username is required"),
  email: Yup.string().email().required("email is required"),
});

...


 <Formik
        initialValues={{ username: "", email: "" }}
        validationSchema={Schema}
        onSubmit={(values) => console.log(values)}
 >
        {({
          handleSubmit,
          handleChange,
          values,
          errors,
          touched
        }) => {

...

{errors.username && touched.username && (
<Text style={styles.text}>{errors.username}</Text>)}{errors.email && touched.email && (
<Text style={styles.text}>{errors.email}</Text>)}

...
```
Here we are marking username and email as required fields, and adding the email method which uses a regex under the hood to validate the email format. Then we pass the schema into Formik's Yup config prop **validationSchema**, which will manage and map the error messages we set in the schema.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/oe9ffxse33e3ibl4807x.png)


## Unit Testing:

Unit testing is the practice of testing small pieces of code on their own, which makes it easier to track, control and refactor the code without any disruption.
We will use **Jest** as our test runner and **Testing Library** for providing the testing utilities needed for catching our components to perform actions upon them.

Let's starts by installing Expo's compatible version of Jest

`npm install jest-expo jest --save-dev`

Then we need to do some configurations in `package.json` and add the testing script

```
{
    ...  

    "scripts": {
      ...
      "test": "jest"
    },
    "jest": {
      "preset": "jest-expo",
      "transformIgnorePatterns": [
        "node_modules/(?!((jest-)?react-native|@react-native(-community)?)|expo(nent)?|@expo(nent)?/.*|@expo-google-fonts/.*|react-navigation|@react-navigation/.*|@unimodules/.*|unimodules|sentry-expo|native-base|react-native-svg)"
      ],
      "collectCoverage": true,
      "collectCoverageFrom": [
        "**/*.{js,jsx}",
        "!**/coverage/**",
        "!**/node_modules/**",
        "!**/babel.config.js",
        "!**/jest.setup.js"
      ]
    },
    ...
  }
 
```
For more info about Jest configurations, visit the official docs [here](https://docs.expo.dev/guides/testing-with-jest/)

Create a new folder `__tests__` in the root directory and inside of it create `App.test.js` which will include our tests.


Let's perform our first test case

Copy the code below into `App.test.js`

```
import React from "react";
import { render, fireEvent, waitFor } from "@testing-library/react-native";
import App from "../App";

describe("Form Validations", () => {
  it("does not show error messages when required values are fullfilled", async () => {
     
    });
  });
});
```

**describe** is used to organize a set of related test scenarios. Here, we are **describing** our testing block as "Form Validations" and it will contain all the positive and negative scenarios for the validations.

**it** is the method where we perform each individual test.

```
import React from "react";
import { render, fireEvent, waitFor } from "@testing-library/react-native";
import App from "../App";

describe("Form Validations", () => {
  it("does not show error messages when required values are fullfilled", async () => {
    const { getByPlaceholderText, queryByText, getByTestId } = render(<App />);

    let usernameInput = getByPlaceholderText("username");
    let emailInput = getByPlaceholderText("email");
    let submitButton = getByTestId("button");
    
    fireEvent.changeText(usernameInput, "Mohamed");
    fireEvent.changeText(emailInput, "mohamed@email.com");

    await waitFor(() => {
      fireEvent.press(submitButton);
    });

    await waitFor(() => {
      expect(queryByText("username is required")).toBeNull();
      expect(queryByText("email is required")).toBeNull();
    });
  });
});
```
Our first scenario is where the user has filled in all the form inputs, so here we are assuming that the required warning does not show up.

We are rendering our form and then using the testing library special queries to get the components we need to perform the test on.

**getByPlaceholderText** searches for the input that holds the matching placeholder we provide.

**getByTestId** gets the component that holds the matching **testID**, so let’s get back to App.js and assign a testID to the **Pressable** component

**fireEvent** is used to invoke a given event handler, so we are using it here to change the input texts and then pressing the submit button that we got above. And we are using waitFor to wait until the press event is fulfilled.

```
...
it("shows error messages when required values are not fullfilled", async () => {
    const { getByPlaceholderText, queryByText, getByTestId } = render(<App />);

    let usernameInput = getByPlaceholderText("username");
    let emailInput = getByPlaceholderText("email");
    let submitButton = getByTestId("button");
    
    fireEvent.changeText(usernameInput, "");
    fireEvent.changeText(emailInput, "");

    await waitFor(() => {
      fireEvent.press(submitButton);
    });

    await waitFor(() => {
      expect(queryByText("username is required")).toBeTruthy();
      expect(queryByText("email is required")).toBeTruthy();
    });
  });
...
```

The second scenario is where the user has not filled the form inputs, so we expect the required errors to show up.

**toBeNull** and **toBeTruthy** are Jest expectations to assume whether a specific component exists or not.

You can find more info about Jest expectation [here](https://jestjs.io/docs/expect)

You have probably noticed how we repeatedly rendered the App component and defined the same variables in both tests, so to avoid the redundancy we use Jest's **beforeEach**

```
...
describe("Form Validations", () => {
  let getByPlaceholderText,
    queryByText,
    getByTestId,
    usernameInput,
    emailInput,
    submitButton;
  beforeEach(() => {
    ({ getByPlaceholderText, queryAllByText, queryByText, getByTestId } =
      render(<App />));
    usernameInput = getByPlaceholderText("username");
    emailInput = getByPlaceholderText("email");
    submitButton = getByTestId("button");
  });

  it("does not show error messages when required values are fullfilled", async () => {
    fireEvent.changeText(usernameInput, "Mohamed");
    fireEvent.changeText(emailInput, "mohamed@email.com");

    await waitFor(() => {
      fireEvent.press(submitButton);
    });

    await waitFor(() => {
      expect(queryByText("username is required")).toBeNull();
      expect(queryByText("email is required")).toBeNull();
    });
  });

  it("shows error messages when required values are not fullfilled", async () => {
    fireEvent.changeText(usernameInput, "");
    fireEvent.changeText(emailInput, "");

    await waitFor(() => {
      fireEvent.press(submitButton);
    });

    await waitFor(() => {
      expect(queryByText("username is required")).toBeTruthy();
      expect(queryByText("email is required")).toBeTruthy();
    });
  });
...
```

Now we perform the couple left scenarios where we expect whether a valid email is provided or not.

```
  it("does not show error message when email is valid", async () => {
    fireEvent.changeText(usernameInput, "Mohamed");
    fireEvent.changeText(emailInput, "mohamed@email.com");

    await waitFor(() => {
      fireEvent.press(submitButton);
    });

    await waitFor(() => {
      expect(queryByText("email must be a valid email")).toBeNull();
    });
  });

  it("shows error message when email is not valid", async () => {
    fireEvent.changeText(usernameInput, "Mohamed");
    fireEvent.changeText(emailInput, "mohamed");

    await waitFor(() => {
      fireEvent.press(submitButton);
    });

    await waitFor(() => {
      expect(queryByText("email must be a valid email")).toBeTruthy();
    });
  });

```

Run the test script and make sure 4 tests have passed successfully!

## Animated API:

Unlike ReactJS and web development, where we use CSS to create animations, React Native require us to use the Animated API to build and maintain animations.

Let's import **Animated** so that we can get started

```
...
import {
  ...
  Animated,
} from "react-native";
...
```

We want to create a smooth transition on rendering the component for the first time, from the bottom of the screen up to the form original position in the center.

```
...
export default function App() {
  const translateY = useRef(new Animated.Value(100)).current;
...
```
So here we create an instance of **Animated.Value** and wrap it in a **useRef** so that it gets created once, and then set the initial value to 100.


```
}) => {
...
          return (
            <Animated.View
              style={{
                transform: [{ translateY }],
              }}
            >
...
            </Animated.View>
```

Then we replace the component that we want to apply the animation on which in this case is **View** with **Animated.View**, and we assign the **translateY** to the animated value instance we just created.

> [Animated](https://reactnative.dev/docs/animated) exports six animatable component types: View, Text, Image, ScrollView, FlatList and SectionList, but you can also create your own using Animated.createAnimatedComponent().

```
...
useEffect(() => {
    Animated.timing(translateY, {
      toValue: 0,
      duration: 500,
      useNativeDriver: true,
    }).start();
  }, []);
...
```
Animated comes with helper functions that provide various animation types, for the on mount animation we will be using **Animated.timing()** which by default uses **easeInOut** curve but you can for sure pick your own [easing function](https://reactnative.dev/docs/easing). After that we set the duration and target value. 

To improve the animation performance, we are activating **useNativeDriver** which will allow native code to perform the animation directly on the UI thread.

> By using the native driver, we send everything about the animation to native before starting the animation, allowing native code to perform the animation on the UI thread without having to go through the bridge on every frame. Once the animation has started, the JS thread can be blocked without affecting the animation.

And finally starting the animations by calling **start()**

Now let's add a pressing effect to the submit button

Let's create a new Animated.Value instance and set the initial value to 0

```
...
  const btnAnimation = useRef(new Animated.Value(0)).current;
...
```
For the pressing effect we need to create scaling animation

```
const scale = btnAnimation.interpolate({
    inputRange: [0, 1],
    outputRange: [1, 0.8],
  });
```
Here we are **interpolating** and mapping animation with the scaling values, in other words, we are simply telling animated to set the scale value to 0.8 when the animated value is 1 and set it back to 1 when the animated value is 0.

You can find more information about interpolation in the official docs [here](https://reactnative.dev/docs/animations#interpolation)

```
...
  const onPressIn = () => {
    Animated.spring(btnAnimation, {
      toValue: 1,
      useNativeDriver: true,
    }).start();
  };
  const onPressOut = () => {
    Animated.spring(btnAnimation, {
      toValue: 0,
      useNativeDriver: true,
    }).start();
  };
...
```

Here we are using the spring animation which provides a [spring physics model](https://miro.medium.com/max/500/1*ROgAmFquVVrDH_O9k16cPA.gif)

```
...
  const AnimatedPressable = Animated.createAnimatedComponent(Pressable);

...
<AnimatedPressable
                  style={[styles.pressable, { transform: [{ scale }] }]}
                  onPress={handleSubmit}
                  onPressIn={onPressIn}
                  testID="button"
                  onPressOut={onPressOut}
                >
                  <Text style={styles.ButtonText}>submit</Text>
                </AnimatedPressable>
...
```
Since Animated is not exporting Pressable we need to use createAnimatedComponent to make the component animatable, then we replace Pressable with the newly created one, and add transform with the scale value assigned to it.

## Snapshot testing:

Now that we have a stable working version of our app, we can perform snapshot testing. Snapshot testing ensures that there won't be any unexpected changes in UI. It typically takes a snapshot of the UI, compares the output with a stored copy, and if it detects any change occurred to the UI, the test simply fails.

```
  it("renders correctly", () => {
    const tree = render(<App />).toJSON();
    expect(tree).toMatchSnapshot();
  });
```

Run the test

A new folder `__snapshots__` should be created in `__tests__`, that's where your snapshot is stored. 

Now try modifying any part of the form UI, and re-run the test

You will find the snapshot test fails!


Full code can be found in the github repo [here](https://github.com/thisisgazzar/react-native-formik-unit-testing).

## Conclusion:

Now that you know the basics of creating an animated form in React Native, and learned how you can cut off a lot of unnecessary code with Formik, you can get the most out of this tutorial by adding more validations to the form and performing unit testing upon them, and then play around with interpolations in Animated.

Have a good one!