React Formik + Yup, onChange touch the field
Asked Answered
G

5

16

I would like to conditionally display errors in my form.

The way formik works is that if you change one field all validations are ran and all errors returned even thought you changed just one.

I would like to display the error only if the field was TOUCHED and I would like a field to be TOUCHED onChange. The first change to the field should make it touched.

At the moment formik is touching fields just on submit. How would I be able to touch it onChange?

This is my current form:

const optionsForSelect = (collection) => {
  return collection.map(item => ({
    value: item.id,
    label: item.name
  }))
}

const validationSchema = yup.object().shape({
  length: yup
    .number()
    .min(1, 'Length should be a positive non-zero integer')
    .required(),
  frame_rate: yup
    .string()
    .required()
})

class SpecificationsForm extends React.PureComponent {
  render() {
    const {
      values,
      handleChange,
      handleInputChange,
      handleSelectChange,
      handleBlur,
      errors,
      touched
    } = this.props;

    const debouncedHandleChange = debounce(handleChange, 200)

    console.log(errors)
    console.log('TOUCHED')
    console.log(touched)
    return (
      <div className="panel panel-default specifications-panel" id="js-turbosquid-product-specifications-panel">
        <div className="panel-heading">
          <a href="#" className="js-more-info" data-toggle="collapse" data-target="#specifications-panel-instructions" tabIndex="-1">
            Specifications
            <i className="fa fa-question-circle" />
          </a>
        </div>

        <div className="panel-body panel-collapse collapse in" id="specification-panel-body">
          <div className="panel-body-container">
            <div id="specifications-panel-instructions" className="panel-instructions collapse" />

            <div className="row">
              <div className="col-xs-6">

                <PanelInputField 
                  label='Length'
                  value={ values.length }
                  onChange={ (e) => handleInputChange(e, debouncedHandleChange) }
                  formName='turbosquid_product_form_length'
                  fieldName='length'
                />

                <div className="form-field-error">{errors.length ? errors.length : "No Error"}</div>

                <PanelSelectField
                  label='Frame Rate'
                  value={ values.frame_rate }
                  onChange={ ({value}) => handleSelectChange('frame_rate', value) } 
                  formName='turbosquid_product_form_frame_rate'
                  fieldName='frame_rate'
                  options={ optionsForSelect(frameRateDropdownData) }
                  searchable={ false }
                  clearable={ false }
                />
              </div>

              <div className="col-xs-6">

                <PanelCheckBox
                  label='Biped'
                  checked={ values.biped }
                  onChange={ (e) => handleInputChange(e, debouncedHandleChange) }
                  fieldName='biped'
                  formName='turbosquid_product_form_biped'
                />

                <PanelCheckBox
                  label='Loopable'
                  checked={ values.loopable }
                  onChange={ (e) => handleInputChange(e, debouncedHandleChange) }
                  fieldName='loopable'
                  formName='turbosquid_product_form_loopable'
                />
              </div>
            </div>
          </div>
        </div>
      </div>
    )
  }
}

const ProductSpecificationsMotionCapturePanel = withFormik({
  validationSchema,
  enableReinitialize: true,
  mapPropsToValues: (props) => (props),
  handleInputChange: (props) => (props.handleInputChange),
  handleSelectChange: (props) => (props.handleSelectChange),
})(SpecificationsForm)

export default ProductSpecificationsMotionCapturePanel
Grume answered 10/9, 2018 at 12:41 Comment(7)
Hi I think it's not doable onChange (if I remember correctly) but you can do so when the input is blurred and you need to use the handleBlur function: onBlur={handleBlur}. Also errors being an object you can display it only when a given [input name] has oneDissemblance
Take a look at when validations are ran here in the docs: jaredpalmer.com/formik/docs/guides/…Dissemblance
If i have let's say a form with 3 fields that are required, when the user starts typing in the first the other 2 are validated also and I end up with more errors than I would like. Let me try the onBlur thing thoughGrume
Nope. Console.log shows an empty touched even with onBlur @DissemblanceGrume
Ok I'm using Formik in a project, with the Formik component, the renderProp and plain inputs and I don't have the same behavior, here's part of it if it can help you: fr.tinypic.com/r/x40hz6/9Dissemblance
Ok, you are right @Dissemblance . I was mistaken with something. You can post this as the right answer. thank you!Grume
Ok glad if it helped you, I'll do that thanks ;)Dissemblance
D
6

Hi I think it's not doable onChange but you can do so when the input is blurred and you need to use the handleBlur function: onBlur={handleBlur}.

Also errors being an object you can display it only when a given [input name] has one.

Take a look at when validations are ran here in the docs: https://jaredpalmer.com/formik/docs/guides/validation#when-does-validation-run

Dissemblance answered 10/9, 2018 at 14:22 Comment(4)
question why a field after it is touched doesn't remain touched? For example i have 2 fields. Both of them must be greater than 0. I input 0 in the first one, the error appears on blur. I go to the 2nd field, i put 1, the error on the first field dissapears seems it is no longer touched. I would like to keep the first field touched as i display the error if field is touched, it should be dirty. Any idea why >Grume
is it because i put enableReinitialize?Grume
I guess so but I haven't used enableReinitialize, for debugging you can JSON.stringify(touched) in your component to see what is going on.Dissemblance
Yes. For all those that have a use case like this(pretty common) seems formik doesn't support it. An answer can be found here with a workaround: github.com/jaredpalmer/formik/issues/163Grume
G
17

To touch a Formik field onChange, you can do this:

<Formik
initialValues={initialValues}
onSubmit={(values) => {
    //submit form
}}>
{({ setFieldTouched, handleChange }) => {
    return (
        <Form>
            <Field
                name="type"
                onChange={e => {
                    setFieldTouched('type');
                    handleChange(e);
                }} />
        </Form>
    )
}}

Grindlay answered 11/10, 2019 at 14:23 Comment(1)
Is there a general property that you can set to automatically touch a field if its changed? I am using my own custom input component by the way.Counteract
D
6

Hi I think it's not doable onChange but you can do so when the input is blurred and you need to use the handleBlur function: onBlur={handleBlur}.

Also errors being an object you can display it only when a given [input name] has one.

Take a look at when validations are ran here in the docs: https://jaredpalmer.com/formik/docs/guides/validation#when-does-validation-run

Dissemblance answered 10/9, 2018 at 14:22 Comment(4)
question why a field after it is touched doesn't remain touched? For example i have 2 fields. Both of them must be greater than 0. I input 0 in the first one, the error appears on blur. I go to the 2nd field, i put 1, the error on the first field dissapears seems it is no longer touched. I would like to keep the first field touched as i display the error if field is touched, it should be dirty. Any idea why >Grume
is it because i put enableReinitialize?Grume
I guess so but I haven't used enableReinitialize, for debugging you can JSON.stringify(touched) in your component to see what is going on.Dissemblance
Yes. For all those that have a use case like this(pretty common) seems formik doesn't support it. An answer can be found here with a workaround: github.com/jaredpalmer/formik/issues/163Grume
H
0

A workaround would be to use formik's method getFieldMeta and pass your field's name and call the value prop which isn't null when you type something.

errorMessage={
 formikProps.getFieldMeta("username").value
  ? formikProps.errors.username
   : ""
}
Hangchow answered 13/11, 2019 at 9:47 Comment(0)
A
0

It's possible to set the touched value without invoking validation again and one can do so by using the useFormik hook available in React 18+.

import { useFormik } from "formik";

const Component = () => {
  const { setFieldTouched, handleChanged } = useFormik({
    validateOnChange: true,
    validateOnBlur: true,
    validateOnMount: true,
  });

  const handleInput = (e) => {
    setFieldTouched(e.target.name, true, false);
    handleChanged && handleChanged(e);
  };
  return <input name="email" onInput={handleInput} />;
};

Amaurosis answered 11/2, 2023 at 21:55 Comment(0)
H
0
 <Grid item xs={12}>
                                                    <Field
                                                        type="email"
                                                        name="email"
                                                        as={FormField}
                                                        label="Email *"
                                                        validate={validate}
                                                        value={emailId}
                                                        onChange={e => {
                                                       setFieldTouched('type');
                                                            handleChange(e)
                                                        }}
                                                    />
                                                    <MDBox>
                                                        <MDTypography component="div" variant="caption" color="error" fontWeight="regular">
                                                            {<div style={{
                                                                cursor: 'pointer'
                                                            }} onClick={emailStatus != 'ACTIVE' ? handleRestore : ''}>{errorMessages}</div>}
                                                        </MDTypography>
                                                    </MDBox>
                                                </Grid>
Humboldt answered 6/4, 2023 at 11:13 Comment(1)
Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.Continence

© 2022 - 2024 — McMap. All rights reserved.