gcanti / tcomb-form

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

Cross validation question #242

Closed patgod85 closed 8 years ago

patgod85 commented 8 years ago

Hi

I have a form:

var DocumentType = t.enums({
    2: 'Birth certificate',
    4: 'Seaman\'s discharge book'
});

var crossValidation = function(p){

    var regexp;

    switch(p.document_type){
        case 2: // Birth certificate
            regexp = /^\w+[А-Я]{2}\d{6}$/;
            break;
        case 4: // Seaman's discharge book
            regexp = /^[\d\w]{2}\d{7}$/;
            break;
    }

    return regexp.test(p.document_number);
};

var Person = t.subtype(
    t.struct({
        document_type: DocumentType,
        document_number: t.String
    }),
    crossValidation
);

Person.getValidationErrorMessage = p => {
    if(!crossValidation(p)){
        return 'Invalid document number format';
    }
};

I understand how to achive such result after press "Save":

reactjs example 01

But I don't see the way to implement so:

reactjs example 02

I need to change validation messages for field _documentnumber depending on field document type. If I use subtype then I can validate whole subtype and show message for whole form. But in my task I must show as invalid only field with document number.

Is it possible?

gcanti commented 8 years ago

Hi, my preferred solution would be to build the type at runtime:

var DocumentType = t.enums({
  '2': 'Birth certificate',
  '4': 'Seaman\'s discharge book'
});

var BirthCertificate = t.refinement(t.String, (s) => /^\w+[А-Я]{2}\d{6}$/.test(s));

var SeamanDischargeBook = t.refinement(t.String, (s) => /^[\d\w]{2}\d{7}$/.test(s));

function getTypeByValue(value) {
  return t.struct({
    document_type: DocumentType,
    document_number: value.document_type === '2' ? BirthCertificate : SeamanDischargeBook
  });
}

const options = {
  fields: {
    document_number: {
      error: 'Invalid document number format'
    }
  }
};

const initialValue = {document_type: '2'};

const App = React.createClass({

  getInitialState() {
    return {
      value: initialValue,
      type: getTypeByValue(initialValue)
    };
  },

  onSubmit(evt) {
    evt.preventDefault();
    var value = this.refs.form.getValue();
    if (value) {
      console.log(value);
    }
  },

  onChange(value) {
    this.setState({
      value,
      type: getTypeByValue(value)
    });
  },

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

});
patgod85 commented 8 years ago

But If I have the list:

var type = t.list(Person)

The way you offered in #238 doesn't work here. Right?

patgod85 commented 8 years ago

I think it can be usefull if we can pass options into validation predicate like that:

https://github.com/patgod85/tcomb-validation/commit/50773f33e1d329c1d73826f93196d3a442a047c8

Then in the client's validation function we can use config of our field like that:

function mysubtype(type, getValidationErrorMessage, name) {
    var Subtype = t.subtype(type, function (x, options) {
        return !t.String.is(getValidationErrorMessage(x, options));
    }, name);
    Subtype.getValidationErrorMessage = getValidationErrorMessage;
    return Subtype;
}

export var Name = mysubtype(t.String, function (s, options) {
    console.log(options);
});
gcanti commented 8 years ago

I think it can be usefull if we can pass options into validation predicate

In tcomb, refinements are defined with a predicate (x: any) => boolean. I can't change this signature in favour of specific tcomb-form requirements.