Skip to content

liveValidate should optionally not validate "pristine" fields (at least until a submit) #512

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

Open
2 tasks done
glasserc opened this issue Mar 14, 2017 · 30 comments
Open
2 tasks done

Comments

@glasserc
Copy link
Contributor

Prerequisites

  • I have read the documentation;
  • In the case of a bug report, I understand that providing a SSCCE example is tremendously useful to the maintainers.

Description

In the playground, the "Validation" tab, when live validation is enabled, starts with an error ("passwords don't match"). However, the user hasn't put in a password yet, so it doesn't make sense as part of the user experience to show this error, at least until they start typing in the field.

Similarly, in the "Errors" tab, the initial state shows a bunch of errors. However, for some user experiences, this doesn't seem necessary or helpful; we're telling the user about errors that pre-existed, not errors they themselves have made.

Per #246 (comment), maybe we should not validate fields until they've been "touched" somehow.

@n1k0
Copy link
Collaborator

n1k0 commented Mar 24, 2017

In the playground, the "Validation" tab, when live validation is enabled, starts with an error ("passwords don't match")

I can't reproduce this behavior with the latest version. Maybe I'm missing a step? If not, how has this been fixed, that would been nice to know.

@n1k0
Copy link
Collaborator

n1k0 commented Mar 24, 2017

Similarly, in the "Errors" tab, the initial state shows a bunch of errors

This one is reproducible, though in this case we provide actual formData, so the form immediately immediately renders errors matching the provided data. This makes sense to me, but maybe just me? If not, would this need to be documented somehow?

@n1k0 n1k0 added the question label Mar 24, 2017
@glasserc
Copy link
Contributor Author

glasserc commented Apr 3, 2017

Ha ha, so apparently my browser had saved a password for localhost:8080 from when I was working on something two jobs ago, and it autofilled that password when I was hacking on rjsf. But obviously it didn't autofill the second one, so "passwords don't match". Never mind...

@glasserc glasserc closed this as completed Apr 3, 2017
@cowbellerina
Copy link
Contributor

The issue still surfaces for example if any of the fields in the "Validation" example are marked as required.

Playground example

In the example, the entire form lights up as the validation fails for the root element (the fieldset containing the fields).

Any ideas on how to get rid of the required errors initially if the form hasn't been touched?

@glasserc
Copy link
Contributor Author

This is a tough one. This example is a great use case for why you might want to live validate passwords as well as required fields, but it's not really great UX to show the user that fields later in the form aren't valid when they haven't even added anything to them. On the other hand, it doesn't make sense to wait until the user "touches" a required field to validate it, because the validation is the mere fact that it hasn't been touched.

Here's an off-the-cuff idea for handling this: maybe we can maintain a state which indicates what the "latest" field that was touched was, and liveValidate only up to that? So for this example, when you enter pass1, we validate the password fields, and then when you get to age, we validate password fields plus the age, and then if you skip ahead to another field, we'd validate password, age, and everything up to the field you were on. I don't think we have the bandwidth to implement anything like this, but I'd try to review a PR..

@glasserc glasserc reopened this Apr 21, 2017
@vamsiampolu
Copy link

Use formContext to maintain the pristine state and use refer to the field's state in the FieldTemplate.

I just provide a Form component wrapper with a formContext that looks like this:

const formContext = {
      lastUpdated: null,
      formControlState: {},
      setTouched: function (id) {
        this.formControlState[id] = 'touched'
      },

      setDirty: function (id) {
        this.formControlState[id] = 'dirty'
      },

      isTouched: function (id) {
        return this.formControlState[id] === 'touched'
      },

      isPristine: function (id) {
        return this.formControlState[id] == null
      },

      isDirty: function (id) {
        return this.formControlState[id] === 'dirty'
      }
    }

I just curry the validate function and pass this formContext to it within Form wrapper component:

const validate = (formContext, formData, errors) => {
  const {password, confirm} = formData
  // hard coding confirm id
  const confirmId = 'root_confirm'
  const confirmModified =
    formContext.isTouched(confirmId) || formContext.isDirty(confirmId)
  if (password != null && confirmModified) {
    if (password !== confirm) {
      // errors.password.addError("Passwords don't match")
      errors.confirm.addError("Passwords don't match")
    }
  }
  return errors
}

Create a higher order component for widgets and fields, do a custom onChange implementation which wraps the onChange provided by the library where update touched, dirty and lastUpdated values within the formContext.

However, I cannot get the name of the field within formData here and must resort to using an id. The library should ideally be implementing this and wrap each widget and field with a HoC to figure out when to perform validation.

Additionally, I am finding it hard to show error on confirm only confirm when password or confirm has been updated (in the example above) so maybe the idea of fields within uiSchema would make sense so that validation occurs only when related fields are changed.

Then we can validate using:

const canShowErrors =
    !isPristine(id) && (id === lastUpdated || isRelatedField)

inside the FieldTemplate component.

@j
Copy link

j commented Oct 3, 2017

I just came across this issue too. My form instantly has errors when no fields have been manipulated.

@glasserc
Copy link
Contributor Author

glasserc commented Oct 6, 2017

@vamsiampolu How would your solution address "required" fields that the user skips over? I agree that validating only "touched" fields is necessary, but not sufficient.

@jannikbuschke
Copy link

jannikbuschke commented Jan 16, 2018

Imho live validation should be trigger if a input control looses focus.
That behaviour has some nice benifits:

  • only touched controls are validated
  • you only get validation errors when you are finished typing, not immediatly

The latter one is a nice thing, as often while typing the editor is in an invalid state (password does not yet match, string is not yet an email-address, and so on)

@rsaiprasad
Copy link

IMHO, instead of modifying the validate function, its better to show or hide errors based on touched or not. A field should always be invalid if it does not have valid data irrespective of touched or not. However, the error message display need not happen if its not touched.

@epicfaace
Copy link
Member

Sounds like this discussion is on the right track. @rsaiprasad @jannikbuschke are you interested in making a PR that implements this change?

@pmonty
Copy link

pmonty commented Oct 28, 2019

Where is this at? Would be nice to only validate touched fields! Happy to get involved if there is a branch open otherwise I can start one. Really would like this feature so I can improve the POC and actually get this library approved for our project going forward.

@priscillacamargo
Copy link

I would to be willing to work on it as well, I am also in need of this functionality for my project.

@epicfaace
Copy link
Member

Feel free to submit a PR / start a branch to work on this issue. It would be good to get it into v2 because it would probably entail a breaking change.

@timkindberg
Copy link

To clarify, I think invalid fields that have not been touched yet would need to wait until submit before they showed their error messages.

@calpa
Copy link

calpa commented Jun 17, 2020

Two years pasted and any update on this issue?

@calpa
Copy link

calpa commented Jun 17, 2020

Good news for the issue followers, I code a fix of this issue.

https://codesandbox.io/s/rjsf-live-validation-ryv3l

@epicfaace
Copy link
Member

@calpa cool, would you like to make a PR?

@calpa
Copy link

calpa commented Jun 17, 2020

@epicfaace I think this is a demo instead of code changes, so need PR?

@ost-ing
Copy link

ost-ing commented Jul 8, 2020

Would be great to have some resolution on this problem, its been open since 2017 and I would consider this a fairly fundamental feature for live-validation to work in a sane way.

@epicfaace epicfaace pinned this issue Aug 30, 2020
@TejeshJadhav-cactus
Copy link

Any updates on this? It's 2021 now!

@jacqueswho jacqueswho unpinned this issue Feb 15, 2021
@Sammii
Copy link

Sammii commented Aug 18, 2021

It's been over 4 years since this request to support very commonly-implemented form functionality. I'm not sure why it isn't a higher priority. Is this issue ever going to be addressed? @epicfaace ?

@epicfaace
Copy link
Member

epicfaace commented Aug 18, 2021 via email

@theexplay
Copy link

Might be useful for someone. Solved a similar problem using a form ref and transformErrors function.

// simplified example
function transformErrors(form: Form<any>) {
  // @ts-expect-error
  const formData = form?.state?.formData ?? {};

  return (errors: AjvError[]) => (
    errors.reduce((errorsList, error) => {
      const isPropDirty = error.property.slice(1) in formData;

      return isPropDirty ? [...errorsList, error] : errorsList;
    }, [])
  )
}

const TemplateForm = (formProps) => {
  // WARN: not sure which type is correct for form ref
  const formRef = useRef<Form<any>>();

  return (
    <Form
      {...formProps}
      ref={formRef}
      liveValidate
      transformErrors={transformErrors(formRef.current)}
    />
}
Gif with example how it works

ezgif-1-385a431b1a

@ltruchot
Copy link

ltruchot commented Mar 7, 2022

Thank you @theexplay for the good work, I think T in formRef should be same type than formData properties of the <Form>

@Dave-Rushabh
Copy link

@theexplay Hi.., can you produce the same in code Sandbox ? i am not getting the exact behaviour as shown in the attached GIF. Just want to check what i am missing to get the similar behaviour.

@theexplay
Copy link

theexplay commented Aug 3, 2022

@Dave-Rushabh sure - https://codesandbox.io/s/serene-archimedes-px0skd?file=/src/TemplateForm.tsx (simplified version from code)

I have wrapper with form data controlled, on change data fire to change state, it rerender Form component and useRef value updated, smth like this)

@Dave-Rushabh
Copy link

@theexplay This works!! Thank you brother..

@beholdr
Copy link

beholdr commented Oct 12, 2022

Might be useful for someone. Solved a similar problem using a form ref and transformErrors function.

But in this case, when we click submit button on the empty form (not touching any fields), we don't see any errors. Especially when noHtml5Validate option used.

I wonder if we can somehow determine if validation process called from liveValidation or from submit. If this possible, then we could show all errors on submit and only touched fields on live validation.

@AndyBoat
Copy link

AndyBoat commented Nov 1, 2023

Thanks to @theexplay , I solved my problem. Now the error list remains hidden until the first submit, and I won't submit if there are any errors even for the first submit.

Example:

const CompiledForm = (props: {
  onSubmit: () => void;
    //... props 
}) => {
  //... business logic
  const formRef = useRef<Form>(null);

  // You have to use Ref here, or ths transformErrors will still return [] on first submit
  // which make validation failed on the first submit
  const hasSubmittedRef = useRef(false);

  const transformErrors = useCallback(
    (errors: RJSFValidationError[]) => (hasSubmittedRef.current ? errors : []),
    []
  );

  return (
    <Form
      // ...
      onSubmit={() => {
        hasSubmittedRef.current = true;
        if (!formRef.current?.validateForm()) {
          return;
        }
        onSubmit();
      }}
      liveValidate={true}
      transformErrors={transformErrors}
    >
      {/* Business Button */}
    </Form>
  );
};

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

No branches or pull requests