Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Auto touch on submit #445

Closed
fkrauthan opened this issue Feb 19, 2018 · 39 comments
Closed

Auto touch on submit #445

fkrauthan opened this issue Feb 19, 2018 · 39 comments

Comments

@fkrauthan
Copy link

Bug, Feature, or Question?

Feature/Question

Current Behavior

I only show error messages when a field is touched and has a error message. The problem when clicking submit it should show error messages for all fields but since they are not getting marked as touched no error shows up

Desired Behavior

When emitting the handleSubmit event all fields in the form should be touched/there should be a flag that indicates if the form was submitted and not reseted.

  • Formik Version: 0.11.10
  • OS: Mac OS
  • Node Version: v9.5.0
  • Package Manager and version: Yarn 1.3.2
@jeffbski
Copy link
Contributor

jeffbski commented Feb 20, 2018

I agree. I just ran into this as well. I would categorize this as a bug unless there is some flag that needs to be set to make this happen.

When we are submitting, we want the invalid fields to display their errors, so they should automatically be touched.

@jeffbski
Copy link
Contributor

Seems like even if I manually have called setFieldsTouched on the fields, then when handleSubmit is called it is clearing out the touched object so then the errors don't show.

@jeffbski
Copy link
Contributor

It looks like the problem occurs when initialValues are not specified.

So when handleSubmit calls setNestedObjectValues with the values, they are {} and so does an Object.keys(values) and comes up with none.

So touched is returned as {}

When using yup schema it should get the fields to touch from the schema rather than the initialValues object.

@fkrauthan
Copy link
Author

I think solutions could be as follow:

  1. Use the error object keys to populate touched
  2. Use the submitted value object keys to populate touched
  3. Add a separate field to specify form fields (with API methods to dynamically add or remove fields)
  4. Add a flag that is true when the form is submitted and gets reseted to false when form gets reseted

All 4 solutions would fix this issue. The last one is probably the easiest to implement.

@jaredpalmer
Copy link
Owner

This behavior exists in formik. All fields are set to touched before submit.

@fkrauthan
Copy link
Author

@jaredpalmer like @jeffbski says it does not work without initializing the form with data.

@jaredpalmer
Copy link
Owner

initialValues are required and should always be specified

@jaredpalmer
Copy link
Owner

It cannot depend on schema because not al users use schema validation

@jeffbski
Copy link
Contributor

@jaredpalmer when a developer does use validationSchema then obviously that can allow formik to know the fields, so if that is supplied it could be used by formik. No it is not required, but frankly I appreciate the value of yup and even the special optional support by formik, so it wouldn't be wrong to use the schema if it is provided.

formik + yup = fantastic

The fact that you provide this as an option gives it an advantage over the other competitors.

@fkrauthan
Copy link
Author

@jaredpalmer The documentation only states that initialValue should be used to prevent possible form state management issues (which in my case do not exist as I use custom forms). Based on what you state you should make clear in the documentation that initialValue is REQUIRED regardless as there is logic (like form submit) based on it. I still think any of my four suggestions would work as well without breaking the current version of the library but give additional flexibility to developers.

@lucasbento
Copy link

@jaredpalmer: initialValues are required and should always be specified

The README.md states that it's optional, is it a typo then?

@armand1m
Copy link

I'm using the withFormik hoc, and there is no initialValues prop. I tried using mapPropsToValues but still it doesn't works as expected :/

@armand1m
Copy link

armand1m commented Jun 12, 2018

Well, after using mapPropsToValues I got a quite different behavior:

  • If none of the fields were touched, clicking on submit do not shows any errors. (which is wrong)
  • If the fields were touched, but no values inserted, and error messages are already showing up, clicking on submit clears any errors (which is wrong)
  • If the fields were touched, values were inserted and cleaned up, and error messages are already showing up, clicking on submit do not clears any errors (which is ok)

Given that, it seems that the touched logic is quite misleading or wrong?

@johnculviner
Copy link

johnculviner commented Jun 20, 2018

After digging into this one for a little bit I think the main reason this is an issue is because Formik doesn't know about the field to set as touched unless you set it on initialValues.

The only partial solution I can think of is to have <Field> (and not everyone uses field) tell Formik about it's self even though it has never set a value or have another mechanism in which to tell Formik about fields that haven't gone through setFieldValue yet.

I'm just doing initialValues={{ohWell: null }} and calling it a day for now.

@flybayer
Copy link

Yeah this is super confusing. I spent a good while wondering why validation on submit wasn't working before finding this thread.

@charlax
Copy link
Contributor

charlax commented Dec 5, 2018

See #691 as well.

@Solavon
Copy link

Solavon commented Aug 16, 2019

Any updates regarding this issue?

@jaredpalmer
Copy link
Owner

initialValues are required.

@Solavon
Copy link

Solavon commented Aug 16, 2019

@jaredpalmer Thank you for the reply, after reading into it more, the behavior that I want given my situation can be achieved by checking the submitCount.
Have a nice day!

@RiccardoCampitelli
Copy link

RiccardoCampitelli commented Nov 7, 2019

I've been using this to set my fields to touched

  const getTouchedObj = (errors: any) => {
    const touched: any = {}
    Object.keys(errors).map(key => {
      if (Array.isArray(errors[key])) {
        errors[key].map((val: any, index: any) => {
          if(index == 0)  touched[key] = [];
          touched[key].push(getTouchedObj(val));
        });
      } else {
        touched[key] = true;
      }
    });

    return touched;
  };

so in my submit I just call

setTouched(getTouchedObj(errors))

I'm pretty new to this so feedback or better ideas are very appreciated 👍

@abdelhalimkhouas
Copy link

Fields that aren't in the initialValues (for exp dynamic fields) object won't be present in "touched" object, when you click submit the touched field will only contain fields that are present in initialValues, for example:
in the example below you will see the errors when submitting and if you check the touched object you will find {name: true, email: true}
<Formik enableReinitialize initialValues={{ name :"", email: "" }} validationSchema={validationSchema} onSubmit={(values) => { console.log('%c⧭', 'color: #731d6d', values); } } >

but in this next example let's assume you will push fields into an array you don't know what fields will be in so the array is initially empty:
var myFields = [] <Formik enableReinitialize initialValues={{ fields: myFields }} validationSchema={validationSchema} onSubmit={(values) => { console.log('%c⧭', 'color: #731d6d', values); } } >

after pushing fields for example name and email into myFields and click submit the fields won't be present in initial values and nor in touched that's why you won't see errors.
in that case you should use errors object

@Srikrishnan96
Copy link

Srikrishnan96 commented Mar 3, 2020

This behavior exists in formik. All fields are set to touched before submit.

Is there any way I can overwrite this behavior ... I have a case where I must validate only fields that are actually touched on submitting ... NOTE:- I am using yup validation object

@DinAlla
Copy link

DinAlla commented Mar 16, 2020

I've been using this to set my fields to touched

  const getTouchedObj = (errors: any) => {
    const touched: any = {}
    Object.keys(errors).map(key => {
      if (Array.isArray(errors[key])) {
        errors[key].map((val: any, index: any) => {
          if(index == 0)  touched[key] = [];
          touched[key].push(getTouchedObj(val));
        });
      } else {
        touched[key] = true;
      }
    });

    return touched;
  };

so in my submit I just call

setTouched(getTouchedObj(errors))

I'm pretty new to this so feedback or better ideas are very appreciated 👍

So, where you get error object? As I understand you call setTouched into function onSubmit, but I don't know where get this?

@RiccardoCampitelli
Copy link

@DinAlla
If I remember correctly the errors are available inside the component like this:

<Formik
 initialValues={yourInitialValues}
onSubmit={yourHandleSubmitMethod}
>
    {({ errors, ...andLotsOfOtherStuff }) => (
          //form markup goes here
       )
    }
</Formik>

Hope this helps!

@StudioSpindle
Copy link

StudioSpindle commented Mar 30, 2020

@DinAlla I guess what you perhaps can do is save the error in state, and then use it in that handler (haven't tested it but this should be the gist):

const getTouchedObj = (errors) => { ... }

const MyForm = () => {
  const [validationErrors, setValidationErrors] = useState();

  return (
    <Formik
      onSubmit={(values, formikBag) => {
        if (validationErrors) {
          formikBag.setTouched(getTouchedObj(validationErrors));
        }
        handleSubmit(values);
      }}
     >
     {(formik) => {
       const { errors } = formik;
       // set the errors here
       setValidationErrors(errors);
     }
   );
};
  >
)

But this is getting messy...

@linjj93
Copy link

linjj93 commented Mar 30, 2020

I'm using the withFormik hoc, and there is no initialValues prop. I tried using mapPropsToValues but still it doesn't works as expected :/

I am having the same issue as well. Anyone managed to get it to work while using withFormik hoc?

@petr-ujezdsky
Copy link

I just hit this 'issue' as well. To be honest, making initial values mandatory is a bit error-prone. You can forget to do it and no error is thrown to indicate your fault.

I have written my own validation library (without knowing Formik exists) and I actually used the same principles.

I have also hit the same problem - I did not know which fields the form has during onSubmit validation. I have solved it by using 'proprietary' error field all. Then eg. the field firstName is touched when touched.firstName || touched.all instead of just touched.firstName. You set touched.all to true on submit, making all the fields "touched" without knowing their names.

Next the all flag should be stored outside the error object to allow usage of all field if it exists (I did it for simplicity).

@konrad-garus
Copy link

Here's a patch that seems to achieve it:

import flatten from 'flat';
const FormikPatchTouched = () => {
  const { errors, setFieldTouched, isSubmitting, isValidating } = useFormikContext();
  useEffect(() => {
    if (isSubmitting && !isValidating) {
      for (const path of Object.keys(flatten(errors))) {
        setFieldTouched(path, true, false);
      }
    }
  }, [errors, isSubmitting, isValidating, setFieldTouched]);
  return null;
};
<Formik>
  {() => {
    return (
      <Form>
        <FormikPatchTouched />
        {/* ... */}
      </Form>
    )
  }}
</Formik>

@dvdprrsh
Copy link

dvdprrsh commented Oct 5, 2020

@konrad-garus Your solution seems to work for me also

@brockklein
Copy link

Here's a patch that seems to achieve it:

import flatten from 'flat';
const FormikPatchTouched = () => {
  const { errors, setFieldTouched, isSubmitting, isValidating } = useFormikContext();
  useEffect(() => {
    if (isSubmitting && !isValidating) {
      for (const path of Object.keys(flatten(errors))) {
        setFieldTouched(path, true, false);
      }
    }
  }, [errors, isSubmitting, isValidating, setFieldTouched]);
  return null;
};
<Formik>
  {() => {
    return (
      <Form>
        <FormikPatchTouched />
        {/* ... */}
      </Form>
    )
  }}
</Formik>

This is a great "fix it all" solution.

If you want to be a bit more targeted and only patch the fields without an initial value (e.g. dynamically added fields from a <FieldArray />), you can make use of submitCount and set touched for those fields equal to submitCount > 0.

@ShimiShimson
Copy link

initialValues are required and should always be specified

If so it would be nice to update Formik docs, it's still marked as optional there

@yizumi1012xxx
Copy link

yizumi1012xxx commented Jul 8, 2021

I've been using this to set my fields to touched

  const getTouchedObj = (errors: any) => {
    const touched: any = {}
    Object.keys(errors).map(key => {
      if (Array.isArray(errors[key])) {
        errors[key].map((val: any, index: any) => {
          if(index == 0)  touched[key] = [];
          touched[key].push(getTouchedObj(val));
        });
      } else {
        touched[key] = true;
      }
    });

    return touched;
  };

I try this code, and I encountered some error.

  • cannot parse nest array structure (e.g. formik.values = { xxx: { yyy: string }[][] } )
  • cannot parse nest object structure (e.g. formik.values = { xxx: { yyy: string } } )

I fix getTouchedObj() function.

const getTouchedObj = (errors: any) => {
+  if (Array.isArray(errors)) {
+    const touched: any = []
+    errors.map((val: any) => {
+      touched.push(getTouchedObj(val))
+    })
+    return touched
+  }
  const touched: any = {}
  Object.keys(errors).map(key => {
    if (Array.isArray(errors[key])) {
      errors[key].map((val: any, index: any) => {
        if (index == 0) touched[key] = []
        const ret = getTouchedObj(val)
        touched[key].push(ret)
      })
+    } else if (typeof errors[key] !== 'string') {
+      touched[key] = getTouchedObj(errors[key])
    } else {
      touched[key] = true
    }
  })
  return touched
}

@yassine-klilich
Copy link

If you are using required attribute or any default browser validation on an input, you have to add noValidate on the form tag, this actually solved my problem, when I submit without touching any input it validate and mark input as touched. @fkrauthan

@shahidcodes
Copy link

If you're wondering why validations are not working on submit, then you might be setting formik values to null or empty object

@GLObus303
Copy link

You can add this to your abstracted form components. Make sure that it does not kill your form performance during the first submit

 useEffect(() => {
      setFieldTouched(id, true)
    }, [submitCount])

you can make it a bit less spammy by wrapping it in touch check

     useEffect(() => {
        if (!touched) {
          setFieldTouched(id, true);
        }
    }, [submitCount]);

You can get both touched and submitCount either from Field/useField or useFormikContext.

To my surprise, the "auto-submit" touching in our case was based on values, not initialValues. We've found out the hard way - when on of the fields were set to undefined via setFieldValue('foobar', someUndefinedProp)

@furdzik
Copy link

furdzik commented Sep 14, 2022

Maybe it will help someone.

In my case (using yup for validation), I needed to define initialValues for separate fields like that:

const initialValues = {
  name: null,
  surname: null,
  phone: null
};

And then it worked as I wanted.

I needed to automatically set all fields as touched when I tried to send field without required fields (to show required fields errors only when submitting without it or after blur on specific field).

Input looked something like that:

<Input
    required
    name="name"
    onChange={(event) => {
       formikProps.setFieldValue('name', event.target.value);
    }}
    label="Input name"
    value={formikProps.values.name}
    error={
       Boolean(formikProps.touched.name)
       && Boolean(formikProps.errors.name)
    }
    helperText={
       formikProps.touched.name? props.formikProps.errors.name: null
    }
    onBlur={formikProps.handleBlur}
 />

@kanonirbrest
Copy link

kanonirbrest commented Nov 7, 2022

Hey guys I found the source of this problem. When you push new arguments to array you should use for example (Use the JSON.stringify() and JSON.parse()) to create copy of array and object (that you are going to push) after set it to formik. In case if you will create
const default = {name: 'name'}; and then do formik.setFieldValue('path', [default, ...formik.values.path]) 2 or 3 times, the touch will ignore last copy. As i understand there are incorrect comparison for objects the has the same source. Hope it will help you. For me JSON.stringify() and JSON.parse()) resolved all problems with validation.
Let me know whether it helps you.

@thautinvio
Copy link

Thanks @kanonirbrest It's working

@buithanhnhan76
Copy link

In my case, the problem is that I not call the formik.submitForm() method

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests