gcanti / tcomb-form

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

Adding has-success on successful validation #299

Closed simonpure closed 8 years ago

simonpure commented 8 years ago

I'm just playing with this very promising library to see how easy it would be to port some Bootstrap/jQuery forms over.

One of the things I'd like to do is adding the has-success style on successful field validation.

What would be the recommended way of extending tcomb-form and/or the bootstrap templates to accomplish this?

Thanks for any pointers!

gcanti commented 8 years ago

Hi @simonpure,

I think that the easiest way to add support for has-success class would be cloning the default templates:

import React from 'react'
import t from 'tcomb-form'

// clone the default templates and add support for has-success class
// this example overrides the default textbox template
const originalRenderFormGroup = t.form.Form.templates.textbox.renderFormGroup
t.form.Form.templates.textbox = t.form.Form.templates.textbox.clone({
  // overrides how the default template renders the form-group container
  renderFormGroup: function renderFormGroup(children, locals) {
    return (
      <div className={!locals.hasError ? 'has-success' : null}>
        {originalRenderFormGroup(children, locals)}
      </div>
    )
  }
})

const Type = t.struct({
  name: t.String,
  surname: t.String
})

const App = React.createClass({

  getInitialState() {
    return {}
  },

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

  onChange(value, path) {
    // validate on each changes
    this.refs.form.getComponent(path).validate()
  },

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

})

The problem I encountered is how to tell the template to not render has-success on first rendering.

simonpure commented 8 years ago

Thanks for the quick response and the sample code @gcanti!

The way it's currently implemented is to trigger validation on keydown and blur individually for each field.

I don't think the same behavior can be achieved without introducing an additional state besides hasError. The way I'm thinking about it is to mark a field as dirty once it has focus and conditionally apply the has-success class (open to alternative suggestions).

Follow up question -

What's the best way to add an additional state to a component?

Thanks!

gcanti commented 8 years ago

I don't think the same behavior can be achieved without introducing an additional state besides hasError

I agree. Yesterday I released v0.8.2 in which, among other things, I added a isPristine boolean (default true) that is turned to false after the first change, so now you can write:

const originalRenderFormGroup = t.form.Form.templates.textbox.renderFormGroup
t.form.Form.templates.textbox = t.form.Form.templates.textbox.clone({
  renderFormGroup: function renderFormGroup(children, locals) {
    const hasSuccess = !locals.isPristine && !locals.hasError
    return (
      <div className={hasSuccess ? 'has-success' : null}>
        {originalRenderFormGroup(children, locals)}
      </div>
    )
  }
})
simonpure commented 8 years ago

Awesome! Just tried it out and it works as expected.

Thanks for the quick addition - much appreciated.

I'm going to close this issue.