Introduction

Sometimes managing forms in react can be a drag, And if you decide to use libraries like redux-form they carry significant performance overhead which you might not be able to afford in the application you are building. Formik is here to your rescue, it is a small library with bundle size of 12 kB compared to redux-form which has a bundle size of 22.5 kB minified gzipped, and the best part; Formik helps with the wearisome task of form handling, which are

Handling form state

Handling form validation and errors

Handling form submission

You can check the docs for more information about the library on Formik

Formik also seamlessly integrates with material-ui; it is a react library that implement Google material design, providing components like input, button, label and several others out of the box.

You can also check out their docs for more information Material-Ui

Finally, there is Yup. What is Yup? It is a JavaScript object schema validator and object parser. In this context Yup simply helps handle validation. This does not mean that you can’t write your own custom validator for Formik but I find my experience using Yup good and it improves the readability of my code.

More on Yup here in the docs Yup.

This article will explain how to build forms and handle form validation with Formik, Yup and Material-UI.

Here is a quick overview of what we are going to do in this guide:

Create a react app using create-react-app.

Create a simple form with Material-UI and Formik.

Write validation rules/ validation schema with Yup.

Use Yup with Formik.

This tutorial assumes you have knowledge of react.

There is a code sandbox demo of the form we are going to build here:

Formik Demo Application

Create react application using CRA Create React App

Create-react-app formik-form-demo



After running this our project structure should look like this:

Now open the App.js file in the src folder and then delete the contents of the parent div that has a className of App.

In your terminal run



Yarn add or npm install formik yup @ material - ui / core

This command adds formik, Yup and material-UI to our dependencies. Now that our dependencies have been installed, create a new folder called InputForm in the src folder then create index.js and form.js files in the InputForm folder.

This is what your src folder should look like now:

The form.js file is going to contain the presentation while the index.js is going to contain most of the logic.

Currently your application should be displaying a blank page, so right now let's just get our form displaying.

In your form.js file add the following code



import React from " react " ; import Button from " @material-ui/core/Button " ; import TextField from " @material-ui/core/TextField " ; export const Form = ( props ) => { return ( < form onSubmit = {() => {}} > < TextField id = " name " name = " name " label = " Name " fullWidth /> < TextField id = " email " name = " email " label = " Email " fullWidth /> < TextField id = " password " name = " password " label = " Password " fullWidth type = " password " /> < TextField id = " confirmPassword " name = " confirmPassword " label = " Confirm Password " fullWidth type = " password " /> < Button type = " submit " fullWidth variant = " raised " color = " primary " > Submit < /Button > < /form > ); };

What we have done here is create a simple form with four fields (Name, Email, Password and Confirm password) and a Button with material-UI.

In index.js file in the InputForm folder add the following code:



import React , { Component } from " react " ; import { Formik } from " formik " ; import withStyles from " @material-ui/core/styles/withStyles " ; import { Form } from " ./form " ; import Paper from " @material-ui/core/Paper " ; const styles = theme => ({ paper : { marginTop : theme . spacing . unit * 8 , display : " flex " , flexDirection : " column " , alignItems : " center " , padding : ` ${ theme . spacing . unit * 5 } px ${ theme . spacing . unit * 5 } px ${ theme . spacing . unit * 5 } px` }, container : { maxWidth : " 200px " } }); class InputForm extends Component { constructor ( props ) { super ( props ); this . state = {}; } render () { const classes = this . props ; return ( < React . Fragment > < div className = { classes . container } > < Paper elevation = { 1 } className = { classes . paper } > < h1 > Form < /h1 > < Formik render = { props => < Form {... props } /> } /> < /Paper > < /div > < /React.Fragment > ); } } export default withStyles ( styles )( InputForm );

Here we have created a class component called InputForm . At the top we imported the form component we just created. And then passed it as a render prop to the Formik component.

There are three ways to render things with Formik

<Formik component />

<Formik render />

<Formik children />

We used the render props in the above. All three render methods will be passed some props which include:

errors

handleChange

handle

isValid

touched

setFieldTouched

There are a couple more props passed to your component, check the docs for all of them Formik Docs

Next go to the App.js file in the src folder, import the InputForm component then add it as a child of the div . This our App.js now and the form should be rendered.



import React , { Component } from ' react ' ; import logo from ' ./logo.svg ' ; import ' ./App.css ' ; import InputForm from ' ./InputForm ' class App extends Component { render () { return ( < div className = " App " > < InputForm /> < /div > ); } } export default App ;

Now We have our form rendered, Let’s start with the form validation. This is where Yup is needed, Basically Yup provides functions that helps us write intuitive validation rules.

First we import Yup into the Index.js file in the InputForm folder then we use its APIs to write our validation rules.

Import statement

import * as Yup from "yup"

Note: importing all the functions/APIs of a library into your codebase is not a good practice.

Now add this following code to the Index.js file in the InputForm folder, This is our validation rules or Validation scheme.



const validationSchema = Yup . object ({ name : Yup . string ( " Enter a name " ) . required ( " Name is required " ), email : Yup . string ( " Enter your email " ) . email ( " Enter a valid email " ) . required ( " Email is required " ), password : Yup . string ( "" ) . min ( 8 , " Password must contain at least 8 characters " ) . required ( " Enter your password " ), confirmPassword : Yup . string ( " Enter your password " ) . required ( " Confirm your password " ) . oneOf ([ Yup . ref ( " password " )], " Password does not match " )

I don’t know about you but at first glance this looks very intuitive. Yup provides several APIs which makes Object validation easy. Some of them are listed below.

APIs

Yup.object() : Is used to define the keys of the object and the schema for those key. In this examples it is used to define the fields we are validating (name, email, password, confirmPassword) and also define validation rules for those fields.

Yup.string() : Defines a string schema. This specifies that field should be a string, it also accepts an optional argument which is used to set the error message. All four fields we defined are strings. Also, we can chain functions or methods so that it is possible have more than one validation rule for each field.

Yup.required() : This specifies that field is required and must not be empty. It also takes an optional argument to define the error message.

Yup.email() : Defines a email schema and also takes an optional argument.

Yup.min() : Sets the minimum length for the value. It accept two arguments, the length and the error message.

Yup.ref() : It creates a reference to another sibling field or sibling descendant field. It accepts a compulsory argument which is the field we are referencing.

Yup.oneOf() : Whitelist a set of values. It accepts an array of the whitelisted value/values and an optional argument that sets the error message.

Check the Docs for a full list of the APIs.

Now that we have defined our validation schema/rules, how do we integrate it into our application?

Remember I earlier said that Yup seamlessly integrates with Formik, well Formik provides a special prop for Yup called validationSchema which will automatically transform Yup's validation errors into a pretty object. So we pass our validation rules to the validationSchema prop. Formik also allows you to set initial value for your fields using the initialValues prop.

So the render function of our InputForm component should look like this when we add the validationSchema and initialValues prop to the Formik component.



render () { const classes = this . props ; const values = { name : "" , email : "" , confirmPassword : "" , password : "" }; return ( < React . Fragment > < div className = { classes . container } > < Paper elevation = { 1 } className = { classes . paper } > < h1 > Form < /h1 > < Formik render = { props => < Form {... props } /> } initialValues = { values } validationSchema = { validationSchema } / > < /Paper > < /div > < /React.Fragment > ); }

We have defined the validation rules and initial values, Now let's use the props passed to the Form component to handle validate the inputs.

In our Form component in the InputForm folder, we destructure the props and create a change function which handles our input changes



const { values : { name , email , password , confirmPassword }, errors , touched , handleSubmit , handleChange , isValid , setFieldTouched } = props ; const change = ( name , e ) => { e . persist (); handleChange ( e ); setFieldTouched ( name , true , false ); };

There are a couple of props passed to the Form component by Formik but I won’t be using all of them in this demo.

Props used are:

values : An object that contains the initial values of the form fields.

errors : An object containing error messages of the field.

touched : An object containing fields that have been touched/visited, fields that have been touched are set to true otherwise they are set to false.

handleChange : General Input handler, This will update the values[key] where key is the event-emitting input's name attribute. If the name attribute is not present, handleChange will look for an input's id attribute.

isValid: Returns true if there are no errors i.e (no errors in the errors object).

setFieldTouched: is a function used to set the touched state of a field. The first argument is the name of the field, the second argument is the value you want to set the touched state to which is true and the last argument is a boolean used to prevent validation.

Now let's make changes to Form component so that we can see the error messages when there’s an error.

Material-UI TextField component provides two props which can help us display our error message in an elegant way, these props are helperText and error for displaying the error.

The Form component should look like this when we add these props to our TextField component.



export const Form = props => { const { values : { name , email , password , confirmPassword }, errors , touched , handleChange , isValid , setFieldTouched } = props ; const change = ( name , e ) => { e . persist (); handleChange ( e ); setFieldTouched ( name , true , false ); }; return ( < form onSubmit = {() => { alert ( " submitted " ); }} > < TextField id = " name " name = " name " helperText = { touched . name ? errors . name : "" } error = { touched . name && Boolean ( errors . name )} label = " Name " value = { name } onChange = { change . bind ( null , " name " )} fullWidth /> < TextField id = " email " name = " email " helperText = { touched . email ? errors . email : "" } error = { touched . email && Boolean ( errors . email )} label = " Email " fullWidth value = { email } onChange = { change . bind ( null , " email " )} / > < TextField id = " password " name = " password " helperText = { touched . password ? errors . password : "" } error = { touched . password && Boolean ( errors . password )} label = " Password " fullWidth type = " password " value = { password } onChange = { change . bind ( null , " password " )} / > < TextField id = " confirmPassword " name = " confirmPassword " helperText = { touched . confirmPassword ? errors . confirmPassword : "" } error = { touched . confirmPassword && Boolean ( errors . confirmPassword )} label = " Confirm Password " fullWidth type = " password " value = { confirmPassword } onChange = { change . bind ( null , " confirmPassword " )} / > < Button type = " submit " fullWidth variant = " raised " color = " primary " disabled = { ! isValid } > Submit < /Button > < /form > ); };

You should notice that I added three props to the Textfield component, helperText , error and onChange .

onChange is set to the change function we wrote above for handling changes to the input field.

The helperText prop is set to a ternary operator (If statement) that states if the field is touched, set the helperText prop to the error message of that field else set it to an empty string. The error prop is set to a boolean to indicate an error in validation.

And finally the Button component has a prop called disabled which disables the button, we set it to not !isValid so if there is any error in the errors object the button remains disabled, I mean we don’t want to be submitting invalid values.

Creating forms with Formik,Material-UI and Yup is awesome.

This is my first technical article/post so I am open to any suggestion that can help improve my writing.

If you have any question or suggestion comment below.

Special thanks to my friend YJTheRuler for editing this article, He writes for @radronline about Afro-beat music and African culture.