gcanti / tcomb-form

Forms library for react
https://gcanti.github.io/tcomb-form
MIT License
1.16k stars 136 forks source link

How to control field validation error message #340

Closed anpr closed 8 years ago

anpr commented 8 years ago

Version

I would like to control the field error message, in my example of the field "supply_code". I'm trying to do that by defining the getValidationErrorMessage function on the type. But it doesn't work for me, instead I get the message Invalid value "126912asdf" supplied to /supply_code: {String | <function1>}. Moreover, it marks the field as being red, but it does not show an error message directly below the Textbox - which is what I would like to have.

Should I instead try to directly control options/fields/supply_code/error in the form_options? But how do I do that?

Example:

const supply_code = t.subtype(t.String, code => /^[0-9]+$/.test(code) && code.length >= 4 && code.length <= 6);
supply_code.getValidationErrorMessage = () => 'Please specify a Supply Code, consisting of 4-6 digits';

const gtinOrSupplierArticleNumber = ({ supplier_article_number, gtin }) => !t.Nil.is(supplier_article_number) || !t.Nil.is(gtin);

const type = t.subtype(t.struct({
    supply_code: supply_code,
    gtin: t.maybe(t.String),
    supplier_article_number: t.maybe(t.String),
  }), gtinOrSupplierArticleNumber);

I get the validation error message using validate():

// ...
    <Form
        ref="productForm"
        type={type}
        options={form_options}
     />
// ...
save() => {
  const productFormValidationResult = this.refs.productForm.validate();
  this.setErrors(productFormValidationResult.errors); // sets the state to contain all errors
  // The problem is that when an invalid supply_code is provided, 
  // at this point productFormValidationResult.errors[0].message 
  // has the above-mentioned string as error message

  // ..
};
gcanti commented 8 years ago

But it doesn't work for me

Just tried your code, works fine for me

this.setErrors(productFormValidationResult.errors); // sets the state to contain all errors

Errors are handled for you by tcomb-form, generally you don't need to manually set the errors

Here's a screenshot

screen shot 2016-06-22 at 17 16 18

and here's my complete code

const supply_code = t.subtype(t.String, code => /^[0-9]+$/.test(code) && code.length >= 4 && code.length <= 6);
supply_code.getValidationErrorMessage = () => 'Please specify a Supply Code, consisting of 4-6 digits';

const gtinOrSupplierArticleNumber = ({ supplier_article_number, gtin }) => !t.Nil.is(supplier_article_number) || !t.Nil.is(gtin);

const Type = t.subtype(t.struct({
  supply_code: supply_code,
  gtin: t.maybe(t.String),
  supplier_article_number: t.maybe(t.String),
}), gtinOrSupplierArticleNumber);

const App = React.createClass({

  onSubmit(evt) {
    evt.preventDefault()
    const v = this.refs.form.getValue()
    if (v) {
      console.log(v)
    }
  },

  render() {
    return (
      <form onSubmit={this.onSubmit}>
        <t.form.Form
          ref="form"
          type={Type}
        />
        <div className="form-group">
          <button type="submit" className="btn btn-primary">Save</button>
        </div>
      </form>
    )
  }

})
anpr commented 8 years ago

Thanks for the complete example. I realized that my example code was too small to show the actual problem. Let me try to add to it.

I added one line to the example:

gtinOrSupplierArticleNumber.getValidationErrorMessage = () => 'Please specify the GTIN or the supplier article number.';

Then I ran the app and entered a valid supply code, but no gtin and no supplier_article_number. The validation failed (as expected), but there was no error message. This is also why I cannot only rely on t-comb-form for the error message. My idea instead was to call validate() and show the user the error messages from the ValidationResult object that comes back.

However, the problem is - also in this particular example - this.refs.form.validate().errors[0].message is:

Invalid value {
  "supply_code": "1234",
  "gtin": null,
  "supplier_article_number": null
} supplied to {Struct{supply_code: {String | <function1>}, gtin: ?String, supplier_article_number: ?String} | gtinOrSupplierArticleNumber}

Which is OK for a default message, but I would have expected 'Please specify the GTIN or the supplier article number' instead.

gcanti commented 8 years ago

getValidationErrorMessage must always added to types, gtinOrSupplierArticleNumber is just a predicate which is used to build the refinement, try this:

const supply_code = t.subtype(t.String, code => /^[0-9]+$/.test(code) && code.length >= 4 && code.length <= 6);
supply_code.getValidationErrorMessage = () => 'Please specify a Supply Code, consisting of 4-6 digits';

const gtinOrSupplierArticleNumber = ({ supplier_article_number, gtin }) => !t.Nil.is(supplier_article_number) || !t.Nil.is(gtin);

const Type = t.subtype(t.struct({
  supply_code: supply_code,
  gtin: t.maybe(t.String),
  supplier_article_number: t.maybe(t.String),
}), gtinOrSupplierArticleNumber);

Type.getValidationErrorMessage = () => 'Please specify the GTIN or the supplier article number.';
gcanti commented 8 years ago

If your error messages are "static" (i.e they don't depend on the current value) you can even get rid of the getValidationErrorMessage functions and use the error option:

const supply_code = t.subtype(t.String, code => /^[0-9]+$/.test(code) && code.length >= 4 && code.length <= 6);

const gtinOrSupplierArticleNumber = ({ supplier_article_number, gtin }) => !t.Nil.is(supplier_article_number) || !t.Nil.is(gtin);

const Type = t.subtype(t.struct({
  supply_code: supply_code,
  gtin: t.maybe(t.String),
  supplier_article_number: t.maybe(t.String),
}), gtinOrSupplierArticleNumber);

const options = {
  error: 'Please specify the GTIN or the supplier article number.',
  fields: {
    supply_code: {
      error: 'Please specify a Supply Code, consisting of 4-6 digits'
    }
  }
}

const App = React.createClass({

  onSubmit(evt) {
    evt.preventDefault()
    const v = this.refs.form.getValue()
    if (v) {
      console.log(v) // eslint-disable-line
    }
  },

  render() {
    return (
      <form onSubmit={this.onSubmit}>
        <t.form.Form
          ref="form"
          type={Type}
          options={options}
        />
        <div className="form-group">
          <button type="submit" className="btn btn-primary">Save</button>
        </div>
      </form>
    )
  }

})
anpr commented 8 years ago

Thanks for your help! You rock :-)

I realized I had a copy&paste error on my code, which was causing the wrong error message. I fixed it, and not it works. Also thanks for your hint regarding setting the "static" error messages.