DEV Community

Nero Adaware
Nero Adaware

Posted on

Formik Material UI React Form using Formik, Material-UI and Yup.

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

Installation:

Create-react-app formik-form-demo

After running this our project structure should look like this:

File structure

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.

App.js

In your terminal run

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

Enter fullscreen mode Exit fullscreen mode

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:

src 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>
 );
};


Enter fullscreen mode Exit fullscreen mode

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




Enter fullscreen mode Exit fullscreen mode

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;

Enter fullscreen mode Exit fullscreen mode

initial form

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


Enter fullscreen mode Exit fullscreen mode

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>
);
}

Enter fullscreen mode Exit fullscreen mode

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);
};


Enter fullscreen mode Exit fullscreen mode

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>
 );
};



Enter fullscreen mode Exit fullscreen mode

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.

working final form

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.

Oldest comments (34)

Collapse
 
crazedvic profile image
Victor R.

Good Article. If Yup.required() is configured for a textfield, meaning it is a required field. How would you pass that information to the TextField so that is renders the label with the * suffix, either ideally before even touching the field but if that's impossible then at the very least after touching? Look here to see what the required field is supposed to look like material-ui.com/demos/text-fields/

Collapse
 
finallynero profile image
Nero Adaware

Thanks. I have not been able to figure out how to do this yet, I will keep looking for solutions. if you find a solution please share.

Collapse
 
faenor profile image
faenor • Edited

As you know beforehand which fields will be required, you can just use the "required" prop on the according TextField.

Collapse
 
fsm1 profile image
Michael Yankelev

Great article. I based my implementation off this, only adding the Formik-Material-UI library to handle the rendering of components.

Collapse
 
baspa profile image
Bas van Dinther

How can I get the values from the form to submit them? And how can I set the value to the state if it has been fetched from the database like I would do in a normal form:

<TextField
id={'name'}
label={'Username'}
name={'name'}
type={'text'}
style={width}
autoComplete={'username'}
value={this.state.name}
onChange={this.handleChange}
/>

Collapse
 
finallynero profile image
Nero Adaware
How can I get the values from the form to submit them?

pass a submit function to formik onSubmit prop.


// submit function
submitValues = ({ name, email, confirmPassword, password }) => {
    console.log({ name, email, confirmPassword, password });
  };
    <Formik
              render={props => <Form {...props} />}
              initialValues={values}
              validationSchema={validationSchema}
              onSubmit={this.submitValues}
            />

Formik then provides you with a handleSubmit prop which you use to handle submit in your form.

 <form onSubmit={handleSubmit}>
......

</form>

handleSubmit basically passes all the values in your form to the submit function you passed to onSubmit

how can I set the value to the state if it has been fetched from the database.

I didn't use state for my initial values in the article but it's use state.

this.state={
name:"",
email:""
}

then in your componentdidmount you can do your database call then setstate and then pass the state values to Formik initialValues prop.

Link to Demo

Collapse
 
baspa profile image
Bas van Dinther • Edited

Thanks for your quick reply. But what if I load the email and username from the state. How can I disable the touched option on those input fields? Because they don't have to be changed everytime but my button keeps disabled.

I tried some things like checking if the state is set and if its set then: this.props.setFieldTouched('email', true, false); but that doesn't seem to work.

Collapse
 
baspa profile image
Bas van Dinther

Could you please provide me more info about the function to check if the field is touched? So I can try to modify it myself?

Thread Thread
 
finallynero profile image
Nero Adaware

Sorry I have be busy recently, Right now I don't think formik has any apis to check if a field is touched.

Collapse
 
finallynero profile image
Nero Adaware
How can I get the values from the form to submit them?

pass a submit function to formik onSubmit prop.


// submit function
submitValues = ({ name, email, confirmPassword, password }) => {
    console.log({ name, email, confirmPassword, password });
  };
    <Formik
              render={props => <Form {...props} />}
              initialValues={values}
              validationSchema={validationSchema}
              onSubmit={this.submitValues}
            />

Formik then provides you with a handleSubmit prop which you use to handle submit in your form.

 <form onSubmit={handleSubmit}>
......

</form>

handleSubmit basically passes all the values in your form to the submit function you passed to onSubmit

how can I set the value to the state if it has been fetched from the database

I didn't use state for my initial values in the article but it's possible use state.

create state values

this.state={
username:''
}

then in your componentdidmount or where ever you make your database calls you can setstate of that value.

componentdidmount(){
//database call
this.setState({username: databaseUsername})
}

then pass it to the Formik's initialValues prop


render(){
const {username} = this.state

const values = {username: username}

return(
<Formik
              render={props => <Form {...props} />}
              initialValues={values}
              validationSchema={validationSchema}
              onSubmit={this.submitValues}
            />

)
}

Link to demo

Collapse
 
bishbashbosh123 profile image
James Robinson

Hey, this is a great tutorial. Many thanks!

With regard to the styling bit, I'm having trouble getting the styles func/object in InputForm/index.js to have any effect. It doesn't seem to have any effect in your code sandbox demo either. This is my first foray with CSS in JS outside of React Native.

Your styles func takes in a theme argument, which lead me to think I need to wrap everything at a higher level with a <MuiThemeProvider />, but I haven't worked out what to give it for it's mandatory 'theme' prop yet. Any advice on this would be great!

Collapse
 
bishbashbosh123 profile image
James Robinson

Predictably, about 10 seconds after posting that comment I found out what the problem was. Looking at the withStyles() docs, I can see that this:

const classes = this.props;

should be...

const {classes} = this.props;

It did seem strange at the time that withStyles would replace all props to the component, rather than just add to them.

Collapse
 
finallynero profile image
Nero Adaware

withStyles will not replace all the props to that component, it should just add to them.

Collapse
 
larissamorrell profile image
Larissa Morrell

Great tutorial!

I'm trying to use a select, and I'm using the material-ui example

<TextField
   select
   id="building-select"
   name="building"
   helperText="Please select the building"
   label="Building"
   value={values.building}
   onChange={change.bind(null, 'building')}
   variant="outlined"
>
   {_.map(buildingArr, building => (
    <MenuItem key={building.uuid} value={building.value}>
      {building.name}
     </MenuItem>
   ))}
</TextField>

But when I use the change() I don't get the value. Any suggestions?

Collapse
 
finallynero profile image
Nero Adaware • Edited

I ran into this problem some time ago. Material ui select don't use the same change handlers like the input and text fields.
Formik provides us with a change handler called(handleChange) as props so you can use that.
That is what i used in the example below

const CreateUserForm = ({ classes }) => {
  const {
    values: { username, password, role },
    errors,
    touched,
    handleChange,
    isValid,
    setFieldTouched,
    handleSubmit,
    isRequesting
  } = props;
  const users = [
    { value: "admin", label: "admin" },
    { value: "cndidate", label: "candate" },
    { value: "examiner", label: "examiner" },
    { value: "examadmin", label: "examadmin" }
  ];
  const change = (name, e) => {
    e.persist();
    handleChange(e);
    setFieldTouched(name, true, false);
  };
  return (
    <form className={classes.form} onSubmit={handleSubmit}>

      <TextField
        id="role"
        select
        label="Role"
        className={classes.textField}
        value={role}
        onChange={handleChange("role")}
        SelectProps={{
          MenuProps: {
            className: classes.menu
          }
        }}
        helperText="Please select your role"
        margin="normal"
        variant="outlined"
      >
        {users.map(option => (
          <MenuItem key={option.value} value={option.value}>
            {option.label}
          </MenuItem>
        ))}
      </TextField>
      <Button
        type="submit"
        fullWidth
        variant="contained"
        color="primary"
        className={classes.submit}
        disabled={!isValid}
      >
        Create User
      </Button>
    </form>
  );
};

so your onChange should be onChange={handleChange('building')} , building being the name for that select

Collapse
 
larissamorrell profile image
Larissa Morrell

So I guess my question now is how can I customize handleChange? For simplicity, what I need to be able to do is make an axios call, sending select's value onChange.

Collapse
 
miguelangeltorresfp profile image
Miguel Angel Torres FP

Thank you very much Nero, you saved my day. I didn't know how to manage form using formik and material-ui. I need a way to subscribe users to a mailchimp list using several fields and this is the only guide I found.

Collapse
 
gopolar profile image
gopolar

A disadvantage of this approach is that you can't really use the Formik's <Field /> component. And for each input field, you need to write a lot of unwanted and repeating code, which I have marked in red.

code

Collapse
 
finallynero profile image
Nero Adaware

Thanks for pointing this out, I haven't used <Field/> component before so I will check it out

Collapse
 
giacomocerquone profile image
Giacomo Cerquone

You can easily use the useField hook and transfer the field object as props to the material-ui TextField.

Take a look at this :)

jaredpalmer.com/formik/docs/api/us...

Collapse
 
grazianospinelli profile image
Graziano Spinelli

Hi! Giacomo, i don't get how to compose usefield hook with material-ui TextField. Can you write a quick example ?

Thread Thread
 
adamlaurencik profile image
Adam Laurenčík • Edited

Hello, I have created an example of how to use an useField hook with material-ui TextField.

Below is a simple FormikTextField component, that can be reused across the whole application.

import React from 'react';
import { useField } from "formik";
import { TextField, TextFieldProps } from '@material-ui/core';


type FormikTextFieldProps = {
    formikKey: string,
} & TextFieldProps

export const FormikTextField = ({ formikKey, ...props }: FormikTextFieldProps) => {
    const [field, meta, helpers] = useField(formikKey);
    return <TextField
        id={field.name}
        name={field.name}
        helperText={meta.touched ? meta.error : ""}
        error={meta.touched && Boolean(meta.error)}
        value={field.value}
        onChange={field.onChange}
        {...props}
    />
}

It is important that this component is nested under the <Formik> component so it can access the necessary properties inside the useField() hook. You can provide additional styling properties to theFormikTextField component similarly as to the original TextField component.

const ExampleComponent = () => (
    <Formik
        initialValues={{ email: "" }}
        validationSchema={validationSchema}
    >
        <FormikTextField formikKey="email" variant="outlined" />
    </Formik>
)
Collapse
 
deebarizo profile image
Dee Barizo

Thanks for this great article. I learned a lot!

Collapse
 
wmoore98 profile image
William Moore

I appreciated the article and was able to successfully integrate Formik, Material-UI, and Yup into my project. I still have a lot more to do, but this was a great starting point to help me connect the dots, so to speak.

Collapse
 
ciptox99 profile image
Haris Sucipto

what happen when we not using e.persist() ?

Collapse
 
webarnes profile image
webarnes • Edited

I wrote a small helper to reduce the boilerplate:

const formikProps = (name, initialValue = '') => ({
    name: name,
    value: typeof values[name] !== 'undefined' ? values[name] : initialValue,
    onChange: handleChange,
    onBlur: handleBlur,
    error: touched[name] && errors[name],
    helperText: touched[name] ? errors[name] : ''
});
Enter fullscreen mode Exit fullscreen mode

And then use the spread operator on your components: <TextField {...formikProps('fieldName')}/>

The initial value is required to tell React it's a controlled component. The above function only works for TextFields (Checkboxes, etc, don't directly support helperText so it would need to be modified).

Collapse
 
finallynero profile image
Nero Adaware

Thanks!!!!

Collapse
 
devorein profile image
Safwan Shaheer

I'd like to add a simple enhancement by adding error: touched[name] && Boolean(errors[name]), this will remove an error from the console

Collapse
 
trondal profile image
Trond Albinussen • Edited

Hi, I have made a version of your package with fully updated package.json. Latest formik, react etc.

No errors, no warnings, everything works as in your somewhat outdated example.
I also fixed some google usability issues.

I have not added anything except fixing breaking changes etc. Will you be interested to have this code?. Then please email me, or send me a message.

Collapse
 
danarj profile image
Danar

thanks sir

Collapse
 
_ali_ profile image
ali

Hi Nero,
I tried using Formik,Yup and MaterialUi together. But it seems like there is a considerable lag when typing in the textfields (of the materialUI) present in the form. The form that I'm using contains many inputs, maybe why the lag is being caused. Any way to avoid this delay ?. Also is there a full rendering of the component, when each and every textfield component is being changed ? as I find the state is being formik state is being updated on every keystroke. If so how can i avoid this. I really don't want my entire component to get rendered on every keystroke :(.

Collapse
 
gkranasinghe profile image
gkranasinghe

HI this may be a silly question , I know this is something to do with object destructuring

What is does values: { name, email, password, confirmPassword }, DO
is it same is props.values.name

const {
values: { name, email, password, confirmPassword },
errors,
touched,
handleSubmit,
handleChange,
isValid,
setFieldTouched
} = props