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 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.
Top comments (34)
I wrote a small helper to reduce the boilerplate:
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).
Thanks!!!!
I'd like to add a simple enhancement by adding
error: touched[name] && Boolean(errors[name])
, this will remove an error from the consoleA 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.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...
Hi! Giacomo, i don't get how to compose usefield hook with material-ui TextField. Can you write a quick example ?
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.
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 originalTextField
component.Thanks for pointing this out, I haven't used
<Field/>
component before so I will check it outHow 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}
/>
How can I get the values from the form to submit them?
pass a submit function to
formik
onSubmit
prop.Formik then provides you with a
handleSubmit
prop which you use to handle submit in your form.handleSubmit
basically passes all the values in your form to the submit function you passed toonSubmit
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
then in your
componentdidmount
or where ever you make your database calls you can setstate of that value.then pass it to the
Formik
'sinitialValues
propLink to demo
How can I get the values from the form to submit them?
pass a submit function to
formik
onSubmit
prop.Formik then provides you with a
handleSubmit
prop which you use to handle submit in your form.handleSubmit
basically passes all the values in your form to the submit function you passed toonSubmit
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.
then in your
componentdidmount
you can do your database call then setstate and then pass the state values toFormik
initialValues
prop.Link to Demo
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.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?
Sorry I have be busy recently, Right now I don't think formik has any apis to check if a field is touched.
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 atheme
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!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.
withStyles
will not replace all the props to that component, it should just add to them.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/
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.
As you know beforehand which fields will be required, you can just use the "required" prop on the according TextField.
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.
Great tutorial!
I'm trying to use a select, and I'm using the material-ui example
But when I use the
change()
I don't get the value. Any suggestions?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
so your onChange should be
onChange={handleChange('building')}
,building
being thename
for that selectSo 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.
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 :(.
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.
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.