formsy-react icon indicating copy to clipboard operation
formsy-react copied to clipboard

Trigger validations onSubmit vs onBlur option

Open th3fallen opened this issue 6 years ago • 7 comments

is this currently possible?

th3fallen avatar May 10 '18 17:05 th3fallen

@th3fallen I am currently using formsy to only show the messages after a user leaves the input (instead of on typing). The validations are still done as the user types but I am only showing them after the user modifies the input or clicks the submit button. I control that in my input wrapper component by choosing when the error message gets displayed or not. Something like:

if (!this.state.isFieldEdited && !this.props.isFormSubmitted()) errorMessage = null;

Note that isFieldEdited is part of my component and isFormSubmitted() is from Formsy.

mikelyncheski avatar May 15 '18 23:05 mikelyncheski

I've been wanting this for a long time too. What @mikelyncheski suggest is certainly possible and a good way to do it, but sometimes you may want to optimize a demanding validation by not doing it on every keyup. Debounce would also be another nice option. We could probably add this as a non-breaking change as a prop which works as before by default, something like:

<SomeFormsyComponent validateOn={'blur'} />

with 'keyup' as default, or whatever is the default atm

MilosRasic avatar May 18 '18 13:05 MilosRasic

Ok, we're all stupid. Sorry for calling you stupid, but I'm calling myself stupid too so it's fine ;)

Upon further investigation turns out this is up to the implementation of a formsy field component. this.props.setValue provided by withFormsy has a secon parameter, boolean, which tells formsy whether to validate the new value or not. This altered example from readme does exactly what we need:

class MyInput extends React.Component {
  constructor(props) {
    super(props);
    this.changeValue = this.changeValue.bind(this);
    this.validateValue = this.validateValue.bind(this);
  }

  changeValue(event) {
    // setValue() will set the value of the component, which in
    // turn will validate it and the rest of the form
    // Important: Don't skip this step. This pattern is required
    // for Formsy to work.
    this.props.setValue(event.currentTarget.value, false);
  }

  validateValue(event) {
    this.props.setValue(event.currentTarget.value);
  }

  render() {
    // An error message is returned only if the component is invalid
    const errorMessage = this.props.getErrorMessage();

    return (
      <div>
        <input
          onChange={this.changeValue}
          onBlur={this.validateValue}
          type="text"
          value={this.props.getValue() || ''}
        />
        <span>{errorMessage}</span>
      </div>
    );
  }
}

MilosRasic avatar May 18 '18 14:05 MilosRasic

As a followup on this one. When you use onBlur to set the error, a common pattern is to clear the error when the user starts typing.

  1. Enter invalid value
  2. Blur the field
  3. Error appears
  4. Change the value in the field
  5. Error message goes away
  6. Blur the field
  7. Validation occurs again

Right now, it seems that Formsy does not internally handle steps 4-5, correct.

djensen47 avatar May 29 '19 17:05 djensen47

Is there any solution for this issue? we actually need to trigger validations onSubmit and onBlur , it's really needed

hungbang avatar Oct 22 '19 13:10 hungbang

@hungbang There is currently no solution for this issue, but PRs are welcome as well as failing tests or even just edits to API.md with the desired feature spec. I think anyone will find the project very easy to work with.

rkuykendall avatar Feb 22 '20 20:02 rkuykendall

Hi, I wanted to implement this behavior so I went through the source code of formsy and came up with this solution, but I don't know much about the structure and the way formsy validates the forms in it's internals (frankly don't have that much time), but it seems to be working as expected.

That's why i wanted to ask @rkuykendall if this is a possible proof of concept that could be implemented.

ValidationInput.js

import { withFormsy } from "formsy-react";
import React from "react";
class ValidationInput extends React.Component {
  constructor(props) {
    super(props);
    this.changeValue = this.changeValue.bind(this);
    this.validateValue = this.validateValue.bind(this);
  }

  changeValue(event) {
    
   // this is an add method via the InputWrapper
   // that resets the validation properties if an error 
   // is present
    if (this.props.showError) {
      this.props.resetValidation();
    }

    // setValue() will set the value of the component, which in
    // turn will validate it and the rest of the form
    // Important: Don't skip this step. This pattern is required
    // for Formsy to work.
    if (this.props.isValidValue(event.currentTarget.value)) {
      this.props.setValue(event.currentTarget.value);
    } else {
      this.props.setValue(event.currentTarget.value, false);
    }
  }

  validateValue(event) {
    this.props.setValue(event.currentTarget.value);
  }

  keyDown = event => {
    // If we press enter, we want to validate the input (omitting
    // the false argument)
    if (event.keyCode == "13") {
      this.props.setValue(event.currentTarget.value);
    } 
  };

  render() {
    // An error message is returned only if the component is invalid
    const {
      errorMessage,
      className,
      showRequired,
      isFormSubmitted,
      showError,
      type,
      value,
      placeholder,
      children
    } = this.props;

    let nClassName = className ? className : "form-control";

    nClassName =
      isFormSubmitted && showRequired
        ? nClassName + " is-invalid"
        : showError
        ? nClassName + " is-invalid"
        : nClassName;

    return (
      <fieldset className="form-group">
        <input
          className={nClassName}
          type={type ? type : "text"}
          value={value}
          onChange={this.changeValue}
          onBlur={this.validateValue}
          placeholder={placeholder}
          onKeyDown={this.keyDown}
        />
        {children ? children : null}
        {errorMessage ? (
          <div className="invalid-feedback">{errorMessage}</div>
        ) : null}
      </fieldset>
    );
  }
}

class WrappedInput extends withFormsy(ValidationInput) {
  resetValidation = () => {
    const { pristineValue } = this.state;
    const { validate } = this.context;

    this.setState({
      isPristine: true,
      isValid: true
    });
  };

  render() {
    const { innerRef } = this.props;
    const propsForElement = {
      ...this.props,
      errorMessage: this.getErrorMessage(),
      errorMessages: this.getErrorMessages(),
      hasValue: this.hasValue(),
      isFormDisabled: this.isFormDisabled(),
      isFormSubmitted: this.isFormSubmitted(),
      isPristine: this.isPristine(),
      isRequired: this.isRequired(),
      isValid: this.isValid(),
      isValidValue: this.isValidValue,
      resetValue: this.resetValue,
      setValidations: this.setValidations,
      setValue: this.setValue,
      showError: this.showError(),
      showRequired: this.showRequired(),
      value: this.getValue(),
      resetValidation: this.resetValidation
    };

    if (innerRef) {
      propsForElement.ref = innerRef;
    }

    return React.createElement(ValidationInput, propsForElement);
  }
}

export default WrappedInput;

And the form source code:

<Formsy onValidSubmit={this.submitForm}>
  <ValidationInput
    name="email"
    validations="isEmail"
    validationError={strings.form.errors.emailFormat}
    placeholder={strings.auth.email}
    required
  />
  <div className="text-center">
    <button
      className="btn btn-primary pull-center w-50 mt-5"
      type="submit"
      disabled={this.props.inProgress}
    >
      {strings.auth.login}
    </button>
  </div>
</Formsy>

EDIT

Added onKeyDown check for validation of the input on enter key press

aqos156 avatar Mar 14 '20 11:03 aqos156