jaredpalmer / formik

Build forms in React, without the tears 😭
https://formik.org
Apache License 2.0
33.88k stars 2.79k forks source link

Validate only one field at a time #512

Open orenklein opened 6 years ago

orenklein commented 6 years ago

Bug, Feature, or Question?

Feature/Question

Current Behavior

Currently, validation is running asynchronously on the whole schema (assuming you use ValidationSchema of yup). It is reasonable to run the validation on submit, however, It is extremely cumbersome to run it on every field change/blur.

Especially if you have some backend validation (using yup.test). Assuming you have a couple of fields, field A, and field B - the later must go through backend validation. Every time you change field A, the validation runs both on A and B which will cause unnecessary backend calls.

I also tried using the new alpha version's handleChange('fieldName') but I still experience the same behavior.

Suggested Solutions

  1. Using yup schema, using yup.reach seems reasonable, even though I'm not sure how is its performance (https://github.com/jaredpalmer/formik/issues/35)

  2. Formik validate function - pass the event's field name. It will allow the developer to validate only one field at a time.

Environment

jaredpalmer commented 6 years ago

We could prototype this with a special version of Field. The tougher bit is how to do it without Yup.

Open to PRs. Also FYI/reference, unlike Field, FastField tries to run validation synchronously before running it async. Perhaps that technique should be moved as the default

jquense commented 6 years ago

For the general case, it seems like just passing fieldName to validate is "good enough" to let someone handle that if they want. You can implement validationSchema in terms of that api for single field validations. It's not particularly hard to do with yup, react-formal implements it like this: https://github.com/jquense/react-formal/blob/master/src/Form.js#L368-L383

jaredpalmer commented 6 years ago

Interesting. Will check this out.

JonathanStoye commented 6 years ago

Any updates on this? Otherwise I would give @jquense idea of passing the fieldName to validate a shot if you don't mind.

EDIT: Nevermind. Since we are only doing client side validation before the submit, touched.email && errors.email is totally working for me. So I am only displaying the errors when the fields have been touched. Of course that is not solving the issue with server side validation. Sorry for making noise here.

sshmyg commented 6 years ago

Any solutions?

sshmyg commented 6 years ago

test method of yup schema invokes even change some other field. It's very inconvenient.

jaredpalmer commented 6 years ago

You can pass a validate function to Field

-- Jared


From: Serhey Shmyg notifications@github.com Sent: Thursday, June 28, 2018 6:02:19 AM To: jaredpalmer/formik Cc: Jared Palmer; Comment Subject: Re: [jaredpalmer/formik] Validate only one field at a time (#512)

Any solutions?

— You are receiving this because you commented. Reply to this email directly, view it on GitHubhttps://github.com/jaredpalmer/formik/issues/512#issuecomment-400982758, or mute the threadhttps://github.com/notifications/unsubscribe-auth/AD30G-ezC6vwgwBIxMYA3GWXf643lt0Tks5uBKmrgaJpZM4SpEE_.

sshmyg commented 6 years ago

@jaredpalmer Can you provide more details, please? I don't use Field component from formik.

sshmyg commented 6 years ago

Oh, you mean I don't need .test in yup schema and just validate in every require field separately?

sshmyg commented 6 years ago

Validate all schema onChange, it's very bad solution.

jaredpalmer commented 6 years ago

See 1.0.0-beta.3 Field validate.

sshmyg commented 6 years ago

I think it's not an option use some Field component. Question was about yup.test validation. You should warn in readme than about using yup. test and 'transform' doesn't work as expected.

jaredpalmer commented 6 years ago

feel free to submit a PR.

You can always use the normal validate function instead of validationSchema and then use Yup to to get the functionality you need.

suberg commented 6 years ago

@jaredpalmer if I pass validate function to my Field components, it runs all these functions when I edit only one field, ON EVERY SINGLE KEYSTROKE. Why? It's not obvious. I don't want to use Yup. Is there a possibility to disable this behaviour and run one validate function?

idesignpixels commented 6 years ago

Would also like a solution to this, my scenario we query a pay as you go api for validation and this would call it over and over with the same value unless we intervene.

sshmyg commented 6 years ago

@idesignpixels Same problem, check user or some other API call will lead to error 503 (Service Unavailable)

jaredpalmer commented 6 years ago

@suberg @idesignpixels let me make sure I am understanding the suggested behavior:

ivanquirino commented 5 years ago

@jaredpalmer let's say we have username and email fields. We set our validation schema to

yup.object().shape({
  email: yup.string().required().email()
}}

Then we pass a custom validation function as validate prop to the username Field that validates if our username is unique. What is happening is that when I type at the email field, the validate function provided to the username field is also running.

stale[bot] commented 5 years ago

Hola! So here's the deal, between open source and my day job and life and what not, I have a lot to manage, so I use a GitHub bot to automate a few things here and there. This particular GitHub bot is going to mark this as stale because it has not had recent activity for a while. It will be closed if no further activity occurs in a few days. Do not take this personally--seriously--this is a completely automated action. If this is a mistake, just make a comment, DM me, send a carrier pidgeon, or a smoke signal.

stale[bot] commented 5 years ago

ProBot automatically closed this due to inactivity. Holler if this is a mistake, and we'll re-open it.

seunggs commented 5 years ago

I'm trying to do async validation using Field's validate and the async call to the server (to check if the email already exists or not) fires for any change in other fields in the form. This is not a great UX.

It'd be great to expose the fieldName to validate on Field so that at least I can check for which field is updating and prevent firing the async call unless it was the field being updated.

asgeirfri commented 5 years ago

I think I have an ok solution for people using <Formik /> and normal input fields

// Custom function to validate single field in validationSchema
  validateSingle = (key, formProps) => {
    validationSchema.validateAt(key, formProps.values).then(() => {
      formProps.setFieldError(key, '')
    }).catch(error => {
      formProps.setFieldError(key, error.message)
    })
  }

Edit: Updated code...

Calling this onBlur should only update the single error field and run the single validation Just pass in the name of the field and the formProps

reach setFieldError

newsroomdev commented 5 years ago

Question @jaredpalmer, are you saying current the current expected behavior for validate on Field components should only run when that specific field is touched? I am using only Field-level validate props with Formik v1.5.1, but I'm also still seeing all validations re-running on change and blur.

Andreyco commented 5 years ago

I think it makes sense to

@jaredpalmer what do you think?

viljark commented 5 years ago

Is there built in way to run only single field validation on change/blur?

@jaredpalmer has stated that "However Formik supports Field-level validation now, which does only run one-at-a-time."

but I'm unable to find any examples and for me all the <Field>s run their validation functions on any field change/blur.

acasaccia commented 5 years ago

I had the same problem as others here, Formik set up with Yup and validationSchema, and a test async validation that was repeated for each keystroke, on other fields too.

I worked this around by invoking my remote validation onBlur:

onBlur={(e) => {
    handleBlur(e);
    $.get('/api/validations/companyName', { value: e.target.value }).then(response => {
        companyNameUnique = response.success;
        validateForm();
    });
}}

I stored the result of validation in a module variable (companyNameUnique) and then forced immediately a full form validation.

This way I can have a sync test in Yup that's simply using the cached result:

companyName: yup.string()
    .test('companyNameUnique', l.companyNameUnique, () => companyNameUnique),
pedroabreu commented 5 years ago

I'm running into the same issue, where all the fields that have been touched are validated onChange and onBlur on any field.

I'm using the validate on the Field with name prop set.

return (
        <Field
            name={name}
            validate={validateFn}
            render={({ field, form }) => (
                <CustomField
                    className={className}
                    childNode={children}
                    displayError={displayError}
                    label={label}
                    labelClassName={labelClassName}
                    meta={{ error: form.errors[field.name], touched: form.touched[field.name] }}
                    required={required}
                />
            )}
        />
    )

validateFn is an async function

From what I could check in the formik code, the validators run for the entire formik state (which is set on touched, on blur and on change). Instead of running it on the entire state, would it make sense to just run it for the field only instead ?

https://github.com/jaredpalmer/formik/blob/master/src/Formik.tsx#L129

Besides on submission, what was the use case to always validate all the fields ? 🤔

pedroabreu commented 5 years ago

I've digged a bit into the docs and code and manage to figure one workaround for single field validation. It seems to be pretty hardcoded to always validate all fields set in state (and touched) in the current implementation so might be tricky to come up with a solution that doesn't involve a prop like validateOnField in the Formik component. Would that be a reasonable solution ?

Anyway, start by disabling form level validation onChange and onBlur

<Formik
    ....
    validateOnChange={false}
    validateOnBlur={false}
>

This will avoid all field validations but will still set the values in the state (plus touched state)

There's an exposed prop called validateField that performs that single field validation once

const onInputBlur = (event, field) => {
    handleBlur(event)
    validateField(field)
}

validateField will handle setting the error in the formik state. So we do the formik handleBlur to set value and touched in state and then call the field validation with the field name.

This way also keeps the full validation on submit (by default).

@jaredpalmer do you think it's a reasonable workaround ? Haven't tested it fully but so far it's been avoiding API validation calls on certain fields (my goal). Maybe I'm missing some case that I haven't thought of.

fenos commented 5 years ago

@pedroabreu it could be a workaround; But just for people that use field level validation.

If you are using validationSchema and yup the validateField function is not an option.

This behaviour of validating fields one by one is very much needed and it should have been the default from the beginning.

At least we should start giving an option for opting out specific fields to be validated in case other fields are changed.

Then trigger validation for that specific field only when interacting with it.

Does anyone have a good workaround for this?!

KosGrillis commented 5 years ago

@pedroabreu

You state

There's an exposed prop called validateField that performs that single field validation once

What do you mean by this? Where is that prop exposed?

pedroabreu commented 5 years ago

In the Formik component at least. Here it is as part of an example for single field validation

https://github.com/jaredpalmer/formik/blob/6899dc5cbd5cc9a818eae5cebbaca9b859483c35/examples/FieldLevelValidation.js

jjalonso commented 5 years ago

@asgeirfri

Based on some experiences with Yup last week, your method will not work when you have Yup schemas that contains when(), and it depends of others, you need to send contexts etc... a problem.

TidyIQ commented 5 years ago

Any updates? I see this issue has gone stale...

erassynathingo commented 5 years ago

i've have a workaround incase anyone is still interested in this.

` <Form.Field error={errors.valueName && touched.valueName !== undefined ? true: false}>

                <Input
                    value={values.valueName}
                    name="valueName"
                    error={errors.valueName && touched.valueName !== undefined ? true: false}
                    onChange={handleChange}
                    onBlur={handleBlur}
                 />
                 {errors.valueName && touched.valueName && <ErrorLabel 
                  children{errors.valueName}/>}

</Form.Field> `

cigzigwon commented 5 years ago

@pedroabreu I agree but it looks like that would produce a new issue due to a race condition, in my case it seems to be referencing prevState when using onChange handler instead of onBlur

cigzigwon commented 5 years ago

@jaredpalmer and @pedroabreu the better workaround would be using custom onChange handler with setFieldValue('borrower_additional', e.target.value, true) which would trigger validation correctly but it looks like using true there does not matter if validateOnChange is false. This seems to be the biggest problem w/Formik because I like others don't use a validation schema and don't need the whole visible form to validate on every single change

cigzigwon commented 5 years ago

@pedroabreu avoid the race condi w/

onChange={async e => {
                        await handleChange(e)
                        validateField('borrower_additional')
                    }}
pedroabreu commented 5 years ago

@cigzigwon I've just spend several hours trying to figure out why was the value validated first and only after set in the state. I already had a codesandbox example to open an issue 😅

For time travelers from the future:

It's weird that I didn't ran into that when on onBlur but only now on the onChange. I'll update the other post I made

silvioBi commented 5 years ago

Any new updates on this one? 😄

cigzigwon commented 5 years ago

If you are having an issue like the latter then you should probably disable validation, events w/false and do field level validation. Ive advised that the recent workaround works just fine as indicated above. Formik will still validate all fields onSubmit

ops1ops commented 5 years ago

Hi guys, i've recently started using formik and now my problem is that when 1 field being changed validation goes on whole form fields so all errors appear at props and my main question is there any solution for this? Formik is really popular, but i didn't find anything in documentation about this.

sshmyg commented 5 years ago

@ops1ops Just use some other lib.

johnrom commented 5 years ago

@ops1ops errors should not appear on the form because the error message component provided by Formik checks to see if a field has been touched (formikProps.touched.myFieldName). If you're using your own message display, you should also check the touched property to only display errors when it is true. Here's an example:

<Formik 
    initialValues={getInitialValues()} 
    validationSchema={getValidationSchema()}
>
    {formikProps => (
        <Form>
            <div>
                <Field name="myField" required />
                {
                    formikProps.touched.myField && 
                    formikProps.errors.myField && 
                    <p>{formikProps.errors.myField}</p>
                }
                // or
                <ErrorMessage name="myField" />
            </div>
        </Form>
    )}
</Formik>

This is documented here: https://jaredpalmer.com/formik/docs/api/errormessage

ops1ops commented 5 years ago

@johnrom thanks for your answer. This is good example, but not really what i need. Currently i have to validate fields onChange, so if i use touched prop i cant really do it, because onChange validation will work only when field was touched. The solution i found is to make your own onChange handler using setFieldValue with setFieldTouched. And now i'm stuck with problem that formik updates all errors when you change field with setFieldValue.

johnrom commented 5 years ago

@ops1ops a field is marked as touched after its first change if you use the API provided by formik. if you use setFieldValue, this might not be the case (code is not in front of me). But it is if you use handleChange. Formik will update all errors when you change a field with setFieldValue, but they will not display on a field until that field is both touched and invalid. If your issue is that there is a performance concern from changing all of the errors, I can understand that. If, however, your issue is that all of the errors will display at once on fields a user hasn't filled out yet, they should not if it is hooked up normally.

ops1ops commented 5 years ago

@johnrom

a field is marked as touched after its first change if you use the API provided by formik.

are you sure about this? For example:

<Form.Item
    validateStatus={touched.name && errors.name ? 'error' : 'success'}
    hasFeedback={touched.name || values.name}
    help={touched.name && errors.name}
>
    <input
        placeholder="name"
        name="name"
        // onChange={ (a) => {
        //     setFieldValue([a.target.name], a.target.value);
        //     setFieldTouched(a.target.name)
        // }}
        onChange={handleChange}
        onBlur={handleBlur}
    />
</Form.Item>

Because even with this default input when you focus it and start changing, errors still dont appear because touched.name is not true. But when you leave input, touched.name becomes true and errors appear. Maybe i don't know something, i've already said that i'm new with formik

johnrom commented 5 years ago

@ops1ops sorry I misspoke, it doesn't set touched until after the field is blurred. This is the default behavior in many forms so that the user is not presented with errors before they've had a chance to finish typing.

ops1ops commented 5 years ago

@johnrom so i was just interested is there anything in formik that provides onChange validation without being blurred. I managed this workaround

        // onChange={ (a) => {
        //     setFieldValue([a.target.name], a.target.value);
        //     setFieldTouched(a.target.name)
        // }}

but i now i have another problems because of it. Maybe you or someone know better workaround

johnrom commented 5 years ago

@ops1ops that's a slightly different topic than this issue, feel free to open a separate issue or join the discord to ask your question. if you open an issue please use the Bug Report template as it has information regarding creating a sandbox and filling out your Formik version information.

ops1ops commented 5 years ago

@johnrom ok, thanks for all