Edit an entry with Formik and Yup doesn't recognize the existing fields

Viewed 5580

I'm using Formik and Yup when I create an entity (company in this case) in order to check if the fields are correct and all of them are introduced (all are required).

When I create an entity it works fine: it only lets you create it if all fields are introduced and the rules are fulfilled (only one rule at the moment for the email).

This is the code which works to create a new company with 2 fields name and email:

    import React from 'react';
    import { Redirect } from 'react-router-dom';
    import { Formik, Form, Field } from 'formik';
    import { Input, Button, Label, Grid } from 'semantic-ui-react';
    import { connect } from 'react-redux';
    import * as Yup from 'yup';
    import { Creators } from '../../../actions';
    import Layout from '../../Layout/Layout';
    
    class CreateCompanyForm extends React.PureComponent {
      constructor(props) {
        super(props);
    
        this.state = {
          name: '',
          contactMail: '',
          redirectCreate: false,
          redirectEdit: false,
          edit: false,
        };
      }
    
      componentDidMount() {
        const {
          getCompany,
          location: { pathname },
        } = this.props;
      }
    
      handleSubmit = values => {
        const { createCompany, getCompanies } = this.props;
        createCompany(values);
        this.setState({ redirectCreate: true });
        getCompanies(this.props.query);
      };
    
      render() {
        let title = 'Create Company';
        let buttonName = 'Create';
        let submit = this.handleSubmitCreate;
    
        const { redirectCreate, redirectEdit } = this.state;
    
        if (redirectCreate) {
          return <Redirect to="/companies" />;
        }
        const initialValues = {
          name: '',
          contactMail: '',
        };
        const requiredErrorMessage = 'This field is required';
        const emailErrorMessage = 'Please enter a valid email address';
        const validationSchema = Yup.object({
          name: Yup.string().required(requiredErrorMessage),
          contactMail: Yup.string()
            .email(emailErrorMessage)
            .required(requiredErrorMessage),
        });
        return (
          <Layout>
            <div>
              <Button type="submit" form="amazing">
                Create company
              </Button>
              <Button onClick={() => this.props.history.goBack()}>Discard</Button>
              <div>Create company</div>
            </div>
    
            <Formik
              htmlFor="amazing"
              initialValues={initialValues}
              validationSchema={validationSchema}
              onSubmit={values => this.handleSubmit(values)}>
              {({ values, errors, touched, setValues }) => (
                <Form id="amazing">
                  <Grid columns={2}>
                    <Grid.Column>
                      <Label>Company Name</Label>
                      <Field name="name" as={Input} placeholder="Hello" />
                      <div>{touched.name && errors.name ? errors.name : null}</div>
                    </Grid.Column>
    
                    <Grid.Column>
                      <Label>Contact Mail</Label>
                      <Field
                        name="contactMail"
                        as={Input}
                        placeholder="johnappleseed@hello.com"
                      />
                      <div>
                        {touched.contactMail && errors.contactMail
                          ? errors.contactMail
                          : null}
                      </div>
                    </Grid.Column>
                  </Grid>
                </Form>
              )}
            </Formik>
          </Layout>
        );
      }
    }
    
    const mapStateToProps = state => ({
      companies: state.companies.companies,
      company: state.companies.selectedCompany,
      query: state.companies.query,
    });
    
    const mapDispatchToProps = {
      getCompanies: Creators.getCompaniesRequest,
      createCompany: Creators.createCompanyRequest,
      getCompany: Creators.getCompanyRequest,
      updateCompany: Creators.updateCompanyRequest,
    };

export default connect(mapStateToProps, mapDispatchToProps)(CreateCompanyForm);

The problem appears when I want to edit the company. So when someone click on a company on edit button it should open the company with all its fields containing the current values which should be editable.

To get those current values I'm using the state, for example the email can be accessed from this.state.email and in order to change the value it was added onChange method.

The values can be modified in the text input. However, it triggers the Yup message that says the field is required even if there is data in it - why is this happening? The field is not empty, that's the situation when it must show that message.

And of course, it doesn't update the entity when I click to save it because it requires those fields.

Here is the code:

import React from 'react';
...

class CreateCompanyForm extends React.PureComponent {
  constructor(props) {
    super(props);

    this.state = {
      name: '',
      contactMail: '',
      redirectCreate: false,
      redirectEdit: false,
      edit: false,
    };
  }

  componentDidMount() {
    const {
      getCompany,
      location: { pathname },
    } = this.props;
    if (pathname.substring(11) !== 'create') { // checks the URL if it is in edit mode 
      getCompany(pathname.substring(16));
      this.setState({
        edit: true,
      });
      this.setState({
        name: this.props.company.name,
        contactMail: this.props.company.contactMail,
      });
    }
  }

    onChange = (e, { name, value }) => { // method to update the state with modified value in input
      this.setState({ [name]: value });
    };

  handleSubmit = values => {
    const { createCompany, getCompanies } = this.props;
    createCompany(values);
    this.setState({ redirectCreate: true });
    getCompanies(this.props.query);
  };

    handleSubmitEdit = e => {
      e.preventDefault();
      const { name, contactMail } = this.state;
      const { updateCompany } = this.props;
      updateCompany(this.props.company._id, {
        name,
        contactMail,
      });
      this.setState({ redirectEdit: true });
    };

  render() {
    let title = 'Create Company';
    let buttonName = 'Create';
    let submit = this.handleSubmitCreate;

    const { redirectCreate, redirectEdit } = this.state;

    if (redirectCreate) {
      return <Redirect to="/companies" />;
    }

    if (redirectEdit) {
      return <Redirect to={`/companies/${this.props.company._id}`} />;
    }

    if (this.state.edit) {
      title = 'Edit Company';
      buttonName = 'Edit';
      submit = this.handleSubmitEdit;
    }

    const initialValues = {
      name: '',
      contactMail: '',
    };
    const requiredErrorMessage = 'This field is required';
    const emailErrorMessage = 'Please enter a valid email address';
    const validationSchema = Yup.object({
      name: Yup.string().required(requiredErrorMessage),
      contactMail: Yup.string()
        .email(emailErrorMessage)
        .required(requiredErrorMessage),
    });
    return (
      <Layout>
        <div>
          <Button type="submit" form="amazing">
            Create company
          </Button>
          <Button onClick={() => this.props.history.goBack()}>Discard</Button>
          <div>Create company</div>
        </div>

        <Formik
          htmlFor="amazing"
          initialValues={initialValues}
          validationSchema={validationSchema}
          onSubmit={values => this.handleSubmit(values)}>
          {({ values, errors, touched, setValues }) => (
            <Form id="amazing">
              <Grid columns={2}>
                <Grid.Column>
                  <Label>Company Name</Label>
                  <Field
                    name="name"
                    as={Input}
                    placeholder="Hello"
                    value={this.state.name || ''} // takes the value from the state
                    onChange={this.onChange} // does the changing 
                  />
                  <div>{touched.name && errors.name ? errors.name : null}</div>
                </Grid.Column>

                <Grid.Column>
                  <Label>Contact Mail</Label>
                  <Field
                    name="contactMail"
                    as={Input}
                    placeholder="johnappleseed@hello.com"
                    value={this.state.contactMail || ''} // takes the value from the state
                    onChange={this.contactMail} // does the changing 
                  />
                  <div>
                    {touched.contactMail && errors.contactMail
                      ? errors.contactMail
                      : null}
                  </div>
                </Grid.Column>
              </Grid>
            </Form>
          )}
        </Formik>
      </Layout>
    );
  }
}

...

export default connect(mapStateToProps, mapDispatchToProps)(CreateCompanyForm);

Any ideas about how to solve this and make the fields editable and remove the 'This field is required' message when the field already has data?

1 Answers

You need to do 3 little changes:

1. Your inital value is always set as:

const initialValues = {
   name: '',
   contactMail: '',
};

You need to change it to:

const initialValues = {
   name: this.state.name,
   contactMail: this.state.contactMail,
};

2. Add enableReinitialize to Formik

Even with the change nº 1, your submit will still throwing errors, that´s because when the component is created, Formik is rendered with the values from your constructor:

this.state = {
      name: "",
      contactMail: "",
      redirectCreate: false,
      redirectEdit: false,
      edit: false,
    };

And when you change state inside componentDidMount, Formik is not reinitialize with the update values:

componentDidMount() {
    const {
      getCompany,
      location: { pathname },
    } = this.props;
    if (pathname.substring(11) !== 'create') { // checks the URL if it is in edit mode 
      getCompany(pathname.substring(16));
      this.setState({
        edit: true,
      });
      this.setState({
        name: this.props.company.name,
        contactMail: this.props.company.contactMail,
      });
    }
  }

So, to formik reinitialize you need to add enableReinitialize to it, like this:

  <Formik
     htmlFor="amazing"
     /// HERE'S THE CODE
     enableReinitialize
     initialValues={initialValues}
     ....

3. With enableReinitialize, Formik will trigger validation on Blur and on Change. To avoid this you can add validateOnChange and validateOnBlur to false:

 <Formik
   htmlFor="amazing"
   enableReinitialize
   validateOnChange={false}
   validateOnBlur={false}
   initialValues={initialValues}
   .....
Related