How to rerun validation on submit in React Final Form
Asked Answered
M

5

5

Once a form's fields have been validated, submitting doesn't trigger a rerun of the validation. Is there a way I can trigger a rerun of the validation when the form is submitted?

I have a form field whose value can become invalid if it's not submitted within a particular timeframe. It's not async; I'm just trying to cover a scenario in which the user doesn't click submit for a while, and when they eventually do, the value would have become invalid. Final form remembers the result of the validation that happens immediately after the value is changed, which means that the unchanged value remains valid regardless of how much time passes between the validation and the submission. This is the behavior I want to hook into and change; the intervening time matters in my use case. I have tried using the beforeSubmit listener from the final-form-submit-listener package but it only gives access to the FormApi object. I tried using the pauseValidation and resumeValidation functions from FormApi but they couldn't achieve what I want, or maybe I'm not using them correctly. I have a feeling it's painfully obvious how to do this, but I can't figure it out. 😩

I created this Sandbox to demonstrate what I mean.

Thanks!

UPDATE: Some additional information:

  • This is for a time picker. If you're picking times for today, you may pick a time that is 15 minutes from now. It's valid now because it's currently in the future. If you don't touch the form for the next 20 minutes then click submit, the submission should be prevented because your selected time is now 5 minutes in the past.
  • I have considered just adding the validation directly in the submit handler. Two answers here do this. However, it is not ideal for me because Final Form doesn't receive the errors and pass them to the meta object for the form fields. My codebase is complex and relies heavily upon the meta object to display error messages. Trying to replicate that functionality in the submit handler may work but it's hacky and goes against the convention used throughout the codebase.
Martie answered 11/7, 2019 at 11:19 Comment(3)
If it's validated once why revalidate? – Westnorthwest
@Westnorthwest As I said above, the intervening time matters in my use case. The value can become invalid after a while, so I want to make sure that it gets checked just before it's submitted. – Martie
Okay, let me see sandbox – Westnorthwest
S
21

Library author here. I'm always fascinated by new ways people can invalidate my assumptions. I mean that in a sincerely positive way, as it results in learning.

🏁 Final Form makes the assumption that your validation functions are "pure" or "idempotent", i.e. will always return the same result when given the same values. This is why it doesn't run the synchronous validation again (just to double check) before allowing the submission: because it's already stored the results of the last time it ran it. By using an outside timer, you've invalidated that assumption.

If you have a better/simpler/"more official" solution, I'd still love to see it!

No need for mutators or decorators for this problem.

The more official way to do this would be to run the check (you could even reuse this.validate) in onSubmit. The only tricky part is that the error will come back as meta.submitError, so you need to check for both when displaying your error. Like so:

Edit Force revalidation on submit (React Final Form)

Simas answered 12/7, 2019 at 11:59 Comment(3)
Fascinating! I never even thought to look into submitError. I'll implement it in my real code first just to be sure it works as I expect it to. Thanks a lot! – Martie
This results in redundant code per component. A validateOnSubmit property would be convenient. – Calmative
Some user experience issue with the proposed solution: This won't show up the inline field error message if you just hit on submit and other fields already contained errors since the code on the onSubmit won't be hit – Undercover
M
4

So I have found a way to do this! πŸŽ‰ I use a mutator and use it's changeValue function to 'change' the value of the relevant field (I supply the same value). This in turn notifies all relevant parties of the change to the form's state, and a validation is triggered. The key is to call the mutator inside the submit handler, which therefore ensures that the validation is performed when the form is submitted. Have a look at this new Sandbox.

The relevant bits are as follows:

// this is a stateful component
...
...
  mutateValue([name], state, { changeValue }) {
    // change the value to the same value, thus
    // triggering a revalidation of the same value
    changeValue(state, name, value => value);
  }

  handleSubmit(values) {
    alert("submitted");
  }

  render() {
    return (
  ...
  ...
        <Form
          onSubmit={this.handleSubmit}
          mutators={{ mutateValue: this.mutateValue }}
          render={({
            handleSubmit,
            form: {
              mutators: { mutateValue }
            }
          }) => {
            const mutateBeforeSubmit = values => {
              // supply the name of the relevant form field
              mutateValue("revalidate");
              // submit handler gets called if revalidation still passes
              handleSubmit(values);
            };
            return (
              <form onSubmit={mutateBeforeSubmit}>
              ...
              ...
              </form>
            );
          }}
        />
        ...
        ...

And because it's triggering the same validation mechanism, meta gets used accordingly!

If you have a better/simpler/"more official" solution, I'd still love to see it!

Martie answered 12/7, 2019 at 8:10 Comment(0)
E
2

You're already putting a function inside onSubmit, why not just add the functionality you want to it? event.preventDefault() and then work with your validate function, it' s a part of the component and accessible to you.

handleOnSubmit(e){
  let value = document.querySelector("input").value 
  if (!!this.validate(value)){
    e.preventDefault();
    alert("Prevented submit event")
  } else{
    alert("Form submitted")
  }
}

now just use this function in the form onSubmit prop(I put in bot since i wasn't sure about the component structure):

<Form onSubmit={this.handleOnSubmit}>...</Form>
<form onSubmit={this.handleOnSubmit}>

And remove the submitListener decorator from the Form component:

decortaor={submitListener}

Now it will check the validation before submitting and prevent it if not validated.

Earn answered 11/7, 2019 at 11:29 Comment(16)
Oh sorry just noticed i'm passing the same form button submit input into validate so let me fix that by finding the required element – Earn
also i don't see any handleSubmit declaration in your file – Westnorthwest
Yeah s/he is just using an arrow function in the onSubmit prop, i assumed based on the code already shown that it's a given for them to put it. But i'll add to the answer anyways. – Earn
Yes @Earn there's an actual handler there in the real code, I only used a simple arrow function for the sandbox. Let me try this out and see. – Martie
The problem is it has Form component that takes onSubmit arrow function BUT the actual form has onSubmit={handleSubmit} which is passed but not declared. – Westnorthwest
@UcheOzoemena I'm a bit lost on where to get the actual input value from, maybe you'd have an easier time with your code to fetch it? – Earn
@UcheOzoemena, update your sandbox when you fix the issues. Plus be little specific is validation based on time or value? a short use case will help us to understand – Westnorthwest
@Earn I pass this.validate to the validate prop of the Field. Final Form therefore passes the input value to this.validate. For the Form itself, in my code it's actually the form values that are passed to the submit handler, rather than an event object. I believe Final Form does preventDefault() automatically, so you can do whatever you like in the submit handler without worrying about it. – Martie
@Westnorthwest it's based on both. It's a time picker, and if you're picking times for today, you may pick a time that is 15 minutes from now. It's valid now because it's currently in the future. If you don't touch the form for the next 20 minutes then click submit, the submission should be prevented because your selected time is now 5 minutes in the past. – Martie
So @Earn your method actually crossed my mind before but I didn't use it because of what I have now confirmed. Basically, the problem is that it's outside the validate prop, so Final Form doesn't receive the errors and pass them to the meta object for the form fields. My codebase is complex and relies heavily upon the meta object to display error messages. Trying to replicate that functionality in the submit handler may work but it's hacky and goes against the convention used throughout the codebase. – Martie
@UcheOzoemena Is the meta responsible for the error message displayed beneath the submit button on error? Because it is working for me in the code sandbox. – Earn
@UcheOzoemena here's a fork with my changes: codesandbox.io/s/… – Earn
@Earn yes meta is responsible for the error message displayed beneath the input. Look at line 110 in my original sandbox. – Martie
Your sandbox is working as I expect, but like I said it's not working with meta. That's important here. – Martie
@UcheOzoemena Alright i lost you on this one then. The errors are being displayed according to the expected behavior. Not sure about other meta functionality you might require. Is the meta supposed to do anything else? – Earn
Lol it's okay, and this thread is getting too long. I created an issue for this on GitHub yesterday. I'll wait until the author of the project responds. Thanks a lot for the effort though! I'll upvote both of your answers. :-) – Martie
U
1

Got another use case where manual triggering validation is needed: In this case we got an student form, in case we got a new student, a field called temporary password must be fullfilled, if not that field is not mandatory, to keep it simple we update the validation schema on the fly (we had a similar case with a validation that needs to be included whenever some fetch operation was completed).

We applied @uche-ozoemena and it worked fine, but @erik-r is this solution considered a hack or is something that we can use as an approved workaround in scenarios where we need to manually trigger validations?

We got something like (using fonk-final-form):

  React.useEffect(() => {
    if (isUserCreation) {
      const newDataFormvalidationSchema = {
        ...dataFormvalidationSchema,
        field: {
          ...dataFormvalidationSchema.field,
          temporaryInitialPassword: [
            ...dataFormvalidationSchema.field.temporaryInitialPassword,
            Validators.required,
          ],
        },
      };

      dataFormValidation.updateValidationSchema(newDataFormvalidationSchema);
    }
  }, [isUserCreation]);

  return (
    <Form
      initialValues={initialData}
      mutators={{ mutateValue: mutateValue }}
      onSubmit={values => {
        save(values);
      }}
      validate={dataFormValidation.validateForm}

Undercover answered 9/1, 2020 at 13:41 Comment(0)
W
0

Since you want to enforce re-validation OR stop submission of form based on the interval, why not use disabled on submit button?

// interval less than 900 = 15 minutes
<button type="submit" disabled={this.state.interval>=900}>
    Submit
</button>

I have also read docs of react-final-form but I thinks it's more easy unless you have a specific case to address using meta.

Westnorthwest answered 11/7, 2019 at 12:5 Comment(7)
Updated :-). I thought on changes it creates fork autmatically :/ – Westnorthwest
Yes this prevents the submission but unfortunately it suffers from the same problem as @Earn 's solution. This doesn't work with meta; I need a solution that does. – Martie
meta ? can you elaborate meta a little? are you talking about meta tags – Westnorthwest
Oh no not meta tags lol. It's an object supplied to each Field component - see the docs here github.com/final-form/react-final-form#fieldrenderprops. I created an issue for this on GitHub yesterday. I'll wait until the author of the project responds. Thanks a lot for the effort though! I'll upvote your answer. :-) – Martie
react-final-form has a detailed docs with examples on sandbox. I played with them. Thanks I may use react-final-form in my projects soon. – Westnorthwest
Nice! Yeah it's great. I just posted the solution I came up with. Have a look. :-) It plays nicely with meta so I'm happy. :-D – Martie
Just saw your update to the answer. Yeah that's another nice idea actually, but it doesn't offer a way to leave an error message using meta. That meta part is key, and it only happens when you hook into Final Form's validation mechanism. My solution does this nicely. :-) – Martie

© 2022 - 2024 β€” McMap. All rights reserved.