DEV Community

React Form using Formik, Material-UI and Yup.

Nero Adaware on October 23, 2018

Introduction Sometimes managing forms in react can be a drag, And if you decide to use libraries like redux-form they carry significant ...
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
 
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
 
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
 
finallynero profile image
Nero Adaware

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

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 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
 
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
 
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
 
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
 
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
 
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
 
_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
 
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
 
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
 
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
 
vinu29011993 profile image
Vinod

Hi sir,
I am building forms in react using Formik and Yup for validations.
I am not getting how we restrict a user to enter limited characters for example,
Mobile Number : user should enter only 10 digits not more that
In my code i am passing max length to input tag
Could you please help me ?

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

Collapse
 
deebarizo profile image
Dee Barizo

Thanks for this great article. I learned a lot!

Collapse
 
sahilbotsync profile image
sahil-botsync

Hi, the former was really descriptive, but how can we use select option components as material UI - formic components?

Collapse
 
danarj profile image
Danar

thanks sir

Collapse
 
ciptox99 profile image
Haris Sucipto

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