简体   繁体   中英

How to clear status before validation on Formik submit?

I'm using Formik with yup validation, following this pattern:

const handleSubmit = async (values, { setStatus }) => {
  setStatus(''); // clean errors messages
  try {
    ... do submit work ...
      const res = await sendData( convertValues(values)); //
    ... more work
  } catch (e) {
    console.error(e);
    setStatus('an error occurred '...);
  }
};

const validationSchema = Yup.object({
  code: Yup.string().required(t('Required')),
  ....
});

return (
    <Formik onSubmit={handleSubmit} initialValues={initialValues} validationSchema={validationSchema}>
      {({ setFieldValue, status }) => (
        <Form>
          ....
          <MyErrorComponent msg={status} />
        </Form>
      )}
    </Formik>
);

This has problems in this scenario:

  1. User submits forms. Validation passes ok, but some error occurs inside handleSubmit . An error message is shown in status
  2. User edits form and retries
  3. This time a validation error occurs
  4. The validation error is displayed inside the input field...but the old status messsage is not cleared.

This happens because validation occurs before submit, hence setStatus('') is not called. What's the recommended way to deal with this?

I think u should use something like this:

<Formik
      initialValues={
        firstName: '',
        email: ''
      }
      validationSchema={
        Yup.object().shape({
          email: Yup.string()
            .email("Must be a valid email")
            .max(255)
            .required("Email is required"),
          firstname: Yup.string()
            .max(255)
            .required("First name is required"),
        })
      }
      onSubmit={(e, { resetForm }) => {
        let val = e;
        postSubmit (e.firstName,e.email);
        // ur rest logic
        // resetForm({
        //  values: {
        //    ...e,
        //  },
        });
      }}
    >
      {({ handleChange, values, initialValues, errors }) => (
        // Your form jsx
      )}
</Formik>

Don't use useState with formik, let Formik take care of error and validation!

I did use this pattern and it works perfectly.

If you didn't find the way, share your form ill make it for you!

It has a validation process on onMouseDown event. You can avoid duplicate validation by prevent default on Submit button.

 onMouseDown={(event: any): void => {
                event.preventDefault();
              }}

after that Formik automatically clear status

you can use hooks for formik useFormik

import { useFormik } from 'formik';
....
const formik = useFormik({
   initialValues: {},
   validationSchema={validationSchema}
   onSubmit: values => {
     handleSubmit(values);
   },
 });

const handleSubmit = async (values, { setStatus }) => {
  try {
    ... do submit work ...
    const res = await sendData( convertValues(values)); //
    ... more work
  } catch (e) {
    console.error(e);
    //change statuvalues or display errors
    //formik.setValue...
    //formik.setError...
  }
};

 
 return (
   <form onSubmit={formik.handleSubmit}>
     ....
     <button type="submit">Submit</button>
   </form>
 );

You can control when Formik runs validation by changing the values of

<Formik validateOnChange> and / or
<Formik validateOnBlur> props depending on your needs. 

By default, Formik will run validation methods as follows:

Pass to your Formik the props

validateOnChange={false} and 
validateOnBlur={false}

For better understanding check the docs Formik Validation

Personally I think that status is only to notify user. So I would notify them from a snackbar / notification by telling them there is an error. Then I would remove them if there's an error on the validation form so they would focus on fixing the wrong one

Here's an example code and sandbox :


const validationSchema = yup.object().shape({
  name: yup.string().required("Required")
});

const initialValues = {
  name: "John Doe"
};

export const FormikTextField = ({ className, ...props }) => {
  const [field, meta] = useField(props);
  const { setStatus } = useFormikContext();

  useEffect(() => {
    if (meta.error) {
      setStatus("");
    }
  }, [meta.error]);

  return (
    <>
      <TextField
        variant="outlined"
        {...field}
        {...props}
        FormHelperTextProps={{ error: true }}
        helperText={meta.error && meta.touched ? String(meta.error) : null}
        aria-invalid={Boolean(meta.error)}
      />
    </>
  );
};

export default function App() {
  const handleSubmit = (values, { setStatus }) => {
    setStatus("status set!");
  };

  return (
    <Formik
      enableReinitialize
      initialValues={initialValues}
      validationSchema={validationSchema}
      onSubmit={handleSubmit}
    >
      {(props) => (
        <Form>
          {props.status && <p>{props.status}</p>}
          <div className="App">
            <FormikTextField name="name" variant="outlined" label="Name" />
            <Button type="submit" variant="contained">
              Submit
            </Button>
          </div>
        </Form>
      )}
    </Formik>
  );
}

Yes, your assumption is correct. Form validation done before submission. What you have to do is, add this errors clearing step in a custom validate function as

const validate = values =>
  {
   // If you want some other custom validations, you can do that on values parameter
   setStatus('');
  };

const handleSubmit = async (values, { setStatus }) => {
//  setStatus(''); // clean errors messages
  try {
    ... do submit work ...
      const res = await sendData( convertValues(values)); //
    ... more work
  } catch (e) {
    console.error(e);
    setStatus('an error occurred '...);
  }
};

const validationSchema = Yup.object({
  code: Yup.string().required(t('Required')),
  ....
});

return (
    <Formik onSubmit={handleSubmit} initialValues={initialValues} validationSchema={validationSchema}
validate={validate}>
      {({ setFieldValue, status }) => (
        <Form>
          ....
          <MyErrorComponent msg={status} />
        </Form>
      )}
    </Formik>
);

For more details, check this CombinedValidations example.

As you are using Async form submission as illustrated in async-submission . You might have to either use async validation function by creating a wrapper Promise like this or make you form submission sync.

Above examples' links are from official docs .

Option 1

You can add validate call in every form input so when ever user change input status message will be cleared.

import React from "react";
import ReactDOM from "react-dom";
import { Formik, Form, Field, ErrorMessage } from "formik";
import * as Yup from "yup";

const schema = Yup.object().shape({
  name: Yup.string().required("Name"),
  age: Yup.number().required("Number").positive().integer()
});

const MyForm = () => (
  <div>
    <Formik
      initialValues={{ name: "", age: "" }}
      validationSchema={schema}
      onSubmit={(values, actions) => {
        console.log("submited", values);
        actions.setStatus("")
        try {
          throw new Error("Something went wrong")
        }
        catch (error) {
          actions.setStatus(error.message);
        }
      }}
      render={({
        status,
        isSubmitting,
        setStatus,
        dirty,
        values,
        handleReset,
        errors,
        touched
      }) => {
        const field_props_with_validation = function() {
          const name = arguments[0];
          const type = arguments[1] || "text";
          const placeholder = arguments[2] || "";
          return {
            name,
            type,
            placeholder,
            validate: () => {
              setStatus("");
            }
          }
        }

        return (
          <Form>
            {status}
            {["name", "age"].map((field, key) => (
              <div key={key}>
                <Field {...field_props_with_validation(field)} />
                <ErrorMessage name={field} component="div" />
              </div>
            ))}
            <button type="submit">Enter</button>
          </Form>
        );
      }}
    />
  </div>
);

function App() {
  return (
    <div className="App">
      <h1>Registration Form</h1>
      <MyForm />
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

Option 2

validation can be disabled on form input change using validateOnChange props and only validate when user submits the form.

Note: In this case you would have to handle the validation, status will not be cleared until user resubmits the form.

import React from "react";
import ReactDOM from "react-dom";
import { Formik, Form, Field, ErrorMessage } from "formik";
import * as Yup from "yup";

const schema = Yup.object().shape({
  name: Yup.string().required("Name"),
  age: Yup.number().required("Number").positive().integer()
});

const MyForm = () => (
  <div>
    <Formik
      initialValues={{ name: "", age: "" }}
      validateOnChange={false}
      onSubmit={async (values, actions) => {
        actions.setStatus("");
        await schema.validate(values, {
          abortEarly: false // not to stop on single validation fail
                            // and return single error message
        })
        .then(() => {
          try {
            throw new Error("Some error")
          }
          catch (error) {
            actions.setStatus(error.message);
          }
        })
        .catch(err => {
          let errors = {};
          Object.keys(values).forEach((key, idx) => {
            if (err && err.errors && err.errors[idx]) {
              errors[key] = err.errors[idx];
            }
          });

          actions.setErrors(errors)
        })
      }}
      render={({
        status,
        isSubmitting,
        dirty,
        values,
        handleReset,
        errors,
        touched
      }) => {
        return (
          <Form>
            {status}
            {["name", "age"].map((field, key) => (
              <div key={key}>
                <Field name={field} type="text" placeholder={field} />
                <ErrorMessage name={field} component="div" />
              </div>
            ))}
            <button type="submit">Enter</button>
          </Form>
        );
      }}
    />
  </div>
);

function App() {
  return (
    <div className="App">
      <h1>Registration Form</h1>
      <MyForm />
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM