Build a better React Native form with Formik and Yup

Prince Shrestha
CodeX
Published in
8 min readMar 26, 2021

“Build forms in React without the tears.” Considering all the tears that typing in all the separate states for each form variable would bring, Formik is not lying with their tagline. This article will go through how to build a Formik form in a more React-(ive) way in React Native coupled with Yup for validation.

By the end of the article, you will have a basic reusable sign-up form using the best code practices. And to achieve that, we will go through these steps:

  1. Create a new react-native project
  2. Install Formik and Yup dependencies
  3. Build a sign-up form
  4. Add Yup validations and pass props to Text Input
  5. Refactor field components
  6. Refactor form components

Project Set-Up
We will be creating a new react-native project for this article using the Typescript template.

Example
npx react-native init MyApp --template react-native-template-typescript
MyCode
npx react-native init DemoSignUpForm --template react-native-template-typescript

After that’s finished, you can simply cd into the project folder or open the folder directly from VsCode (or whichever code-editor you are using).

cd DemoSignUpForm 
OR
cd .\DemoSignUpForm\

Hit yarn start in one terminal and start the app on Android or iOS.

Terminal 1
yarn start
Terminal 2 (for running on virtual android emulator)
npx react-native run-android

Cool, we have the basic react native project done.

Installing Formik and Yup

Now, we need to install two dependencies:

Formik
yarn add formik
Yup
yarn add yup

Easy! Now, let’s actually start building the form.

Building the sign-up form
We’ll start with something functional, nothing you’d want to show to a project demo, but we’ll get there as well.

Start by creating a src folder (if there is not one already) and, inside src, a components folder. This will house our SignUpForm.tsx and, later, our refactored Form components.

Folder structure for project

In the picture, we see a Form folder inside component as well. We’ll come back to that later.

Now, add the following code to SignUpForm.tsx.

import React from 'react';import {Button, Text, TextInput, View} from 'react-native';import {Formik} from 'formik';const SignUpForm = () => {return (<><Text>Sign Up</Text><FormikinitialValues={{name: '', email: '', password: ''}}onSubmit={values => console.log(values)}>{({handleChange,handleBlur,handleSubmit,values,errors,touched,})=> (<View><TextInputplaceholder="Name"onChangeText={handleChange('name')}onBlur={handleBlur('name')}value={values.name}/><TextInputplaceholder="Email"onChangeText={handleChange('email')}onBlur={handleBlur('email')}value={values.email}/><TextInputplaceholder="Password"onChangeText={handleChange('password')}onBlur={handleBlur('password')}value={values.password}/><Button onPress={handleSubmit} title="Submit" /></View>)}</Formik></>);};export default SignUpForm;

Also, we’ll trim down App.tsx to only show the SignUpForm.

import React from 'react';import {SafeAreaView} from 'react-native';import SignUpForm from './src/components/SignUpForm';const App = () => {return (<SafeAreaView><SignUpForm /></SafeAreaView>);};export default App;
Basic form

Boom! We have a form. Let’s add some validations now.

Adding Yup validations and TextInput props
We added a few fields for now, so let’s set some validation logic for each of them.

Start by importing Yup.

import * as Yup from 'yup';

And we’ll write a validation schema outside the SignUpForm function.

const validationSchema = Yup.object().shape({name: Yup.string().required('Name is required').label('Name'),email: Yup.string().email('Please enter valid email').required('Email is required').label('Email'),password: Yup.string().matches(/\w*[a-z]\w*/, 'Password must have a small letter').matches(/\w*[A-Z]\w*/, 'Password must have a capital letter').matches(/\d/, 'Password must have a number').min(8, ({min}) => `Password must be at least ${min} characters`).required('Password is required').label('Password'),});

The yup docs (https://www.npmjs.com/package/yup) will help make better sense of the code block above. In a nutshell, the beauty of yup lets us write a rule and a message to display to the user if the rule is not met.

Now, we move on to using Yup with Formik. Firstly, pass the validationSchema to Formik’s validationSchema prop.

<FormikinitialValues={{name: '', email: '', password: ''}}validationSchema={validationSchema}onSubmit={values => console.log(values)}>

Then, we will display the error messages using the ‘errors’ prop. For now, let's add a <Text> component after every field.

<TextInputplaceholder="Name"onChangeText={handleChange('name')}onBlur={handleBlur('name')}value={values.name}/><Text style={{color: 'red'}}>{errors.name}</Text>

Doing so for every field, we should have something like this.

Error messages

However, we don’t want to show these errors every time, only when there is an error and if the field is touched. We will wrap our Text component with some ES6 code and only render it if the conditions mentioned before are met.

{errors.name && touched.name && (<Text style={{color: 'red'}}>{errors.name}</Text>)}

The validations are done. Now, you might notice, the password is not hidden as we expect it to. We need to pass the secureTextEntry prop to the TextInput for that to happen. We will also pass some other props so that our TextInput behaves correctly.

SignUpForm.tsx will look like so in the end.

import React from 'react';import {Button, Text, TextInput, View} from 'react-native';import {Formik, Field} from 'formik';import * as Yup from 'yup';const validationSchema = Yup.object().shape({name: Yup.string().required('Name is required').label('Name'),email: Yup.string().email('Please enter valid email').required('Email is required').label('Email'),password: Yup.string().matches(/\w*[a-z]\w*/, 'Password must have a small letter').matches(/\w*[A-Z]\w*/, 'Password must have a capital letter').matches(/\d/, 'Password must have a number').min(8, ({min}) => `Password must be at least ${min} characters`).required('Password is required').label('Password'),});const SignUpForm = () => {return (<><Text>Sign Up</Text><FormikinitialValues={{name: '', email: '', password: ''}}validationSchema={validationSchema}onSubmit={values => console.log(values)}>{({handleChange,handleBlur,handleSubmit,values,errors,touched,}) => (<View><TextInputplaceholder="Name"onChangeText={handleChange('name')}onBlur={handleBlur('name')}value={values.name}autoCorrect={false}/>{errors.name && touched.name && (<Text style={{color: 'red'}}>{errors.name}</Text>)}<TextInputplaceholder="Email"onChangeText={handleChange('email')}onBlur={handleBlur('email')}autoCapitalize="none"autoCompleteType="email"autoCorrect={false}keyboardType="email-address"textContentType="emailAddress"value={values.email}/>{errors.email && touched.email && (<Text style={{color: 'red'}}>{errors.email}</Text>)}<TextInputplaceholder="Password"onChangeText={handleChange('password')}onBlur={handleBlur('password')}autoCapitalize="none"secureTextEntrytextContentType="password"value={values.password}/>{errors.password && touched.password && (<Text style={{color: 'red'}}>{errors.password}</Text>)}<Button onPress={handleSubmit} title="Submit" /></View>)}</Formik></>);};export default SignUpForm;

We have a fully functional form, but the code is still not up to the mark.

Refactoring field components
We’ll use some React magic and Formik’s Field component to remove any repeated code and trim it.

Let’s start by creating a new AppFormField component inside the Form folder from before. We’ll copy everything from one of the field components and paste it there.

import React from 'react';import {Text, TextInput} from 'react-native';const AppFormField = (props: any) => {const {placeholder,field: {name, onBlur, onChange, value},form: {errors, touched, setFieldTouched},...inputProps} = props;const hasError = errors[name] && touched[name];return (<><TextInputplaceholder={placeholder}onChangeText={text => onChange(name)(text)}onBlur={() => {setFieldTouched(name);onBlur(name);}}autoCapitalize="none"autoCorrect={false}value={value}{...inputProps}/>{hasError && <Text style={{color: 'red'}}>{errors[name]}</Text>}</>);};export default AppFormField;

Okay, a bit of explaining to do. The props you see are provided by the Field component from Formik that we will add in a while. It allows us to use the formik functions that we were using previously (For detailed information, see https://formik.org/docs/api/field). You’ll notice we have inputProps, which houses the props that we added to TextInput before, which we will now pass to the Field component.

Add this to the formik import.

import {Formik, Field} from 'formik';

We will pass our custom AppFormField component to the Field component to get something like this.

import React from 'react';import {Button, Text, TextInput, View} from 'react-native';import {Formik, Field} from 'formik';import * as Yup from 'yup';import AppFormField from './Form/AppFormField';const validationSchema = Yup.object().shape({name: Yup.string().required('Name is required').label('Name'),email: Yup.string().email('Please enter valid email').required('Email is required').label('Email'),password: Yup.string().matches(/\w*[a-z]\w*/, 'Password must have a small letter').matches(/\w*[A-Z]\w*/, 'Password must have a capital letter').matches(/\d/, 'Password must have a number').min(8, ({min}) => `Password must be at least ${min} characters`).required('Password is required').label('Password'),});const SignUpForm = () => {return (<><Text>Sign Up</Text><FormikinitialValues={{name: '', email: '', password: ''}}validationSchema={validationSchema}onSubmit={values => console.log(values)}>{({handleChange,handleBlur,handleSubmit,values,errors,touched,}) => (<View><Field component={AppFormField} name="name" placeholder="Name" /><Fieldcomponent={AppFormField}name="email"placeholder="Email"autoCompleteType="email"keyboardType="email-address"textContentType="emailAddress"/><Fieldcomponent={AppFormField}name="password"placeholder="Password"secureTextEntrytextContentType="password"/><Button onPress={handleSubmit} title="Submit" /></View>)}</Formik></>);};export default SignUpForm;

See, much better. What this also does is remove the need for those formik props. You will see that all the props except for handleSubmit are grayed out. We can get rid of that as well.

Please make a new file inside the Form folder and name it AppFormSubmitButton.tsx. We’ll copy our component to that file, and we will also use Formik hook to access the handleSubmit function.

import React from 'react';import {Button} from 'react-native';import {useFormikContext} from 'formik';interface AppFormSubmitButtonProps {title: string;}const AppFormSubmitButton = ({title}: AppFormSubmitButtonProps) => {const {handleSubmit, isValid} = useFormikContext();return <Button onPress={handleSubmit} title={title} disabled={!isValid} />;};export default AppFormSubmitButton;

Let's even use the isValid property to disable the button until all user inputs are valid and make the title dynamic. Replace the previous Button component with our new AppFormSubmitButton, and we will no longer need any of the Formik props!

Refactoring the Form
Now let’s move towards removing the weird Formik syntax.

This is what I’m talking about.

Weird Formik syntax

We can easily remove that now that we do not use any of the Formik props.

Let’s create another file in our Form folder and name it AppForm.

import React from 'react';import {Formik} from 'formik';const AppForm = ({initialValues,onSubmit,validationSchema,children,}: any) => {return (<FormikinitialValues={initialValues}onSubmit={onSubmit}validationSchema={validationSchema}>{() => <>{children}</>}</Formik>);};export default AppForm;

The beauty of this is next time we need to make a form, we need to supply the initialValues, onSubmit function, and the validation logic to this wrapper as props, then add whatever field inside it.

Let's add a new field, Confirm Password, to see how simple and clean our new form component is.

Our new SignUpForm.tsx will look like this now.

import React from 'react';import {Text} from 'react-native';import {Field} from 'formik';import * as Yup from 'yup';import AppFormField from './Form/AppFormField';import AppFormSubmitButton from './Form/AppFormSubmitButton';import AppForm from './Form/AppForm';const validationSchema = Yup.object().shape({name: Yup.string().required('Name is required').label('Name'),email: Yup.string().email('Please enter valid email').required('Email is required').label('Email'),password: Yup.string().matches(/\w*[a-z]\w*/, 'Password must have a small letter').matches(/\w*[A-Z]\w*/, 'Password must have a capital letter').matches(/\d/, 'Password must have a number').min(8, ({min}) => `Password must be at least ${min} characters`).required('Password is required').label('Password'),confirmPassword: Yup.string().oneOf([Yup.ref('password')], 'Passwords do not match').required('Confirm password is required').label('Confirm Password'),});const SignUpForm = () => {return (<><Text>Sign Up</Text><AppForminitialValues={{name: '', email: '', password: '', confirmPassword: ''}}validationSchema={validationSchema}onSubmit={(values: any) => console.log(values)}><Field component={AppFormField} name="name" placeholder="Name" /><Fieldcomponent={AppFormField}name="email"placeholder="Email"autoCompleteType="email"keyboardType="email-address"textContentType="emailAddress"/><Fieldcomponent={AppFormField}name="password"placeholder="Password"secureTextEntrytextContentType="password"/><Fieldcomponent={AppFormField}name="confirmPassword"placeholder="Confirm Password"secureTextEntrytextContentType="password"/><AppFormSubmitButton title="Submit" /></AppForm></>);};export default SignUpForm;

We now have a functional form and a neat code as well. You can easily customize how your form looks now.

You can find all the code up to now here =>https://github.com/shresthaprince/rnsignupdemo (in the main branch)

Let me know how you found the tutorial and if you wanted me to make another one on some specific topic.

--

--

Prince Shrestha
CodeX
Writer for

wants to help make your JS journey much smoother. Support me by following me or buying me a small picollo for the mornings https://ko-fi.com/shresthaprince