Bootstrap forms using React.js

Another small design note from my side-project: shipping a library of Bootstrap components wrapped using React.js. In this post, forms.

Using my React.js components, a log in form looks like this:


<Form bsStyle="inline" callback={this.handleLogin}>
  <Input name="email" type="email" placeholder="Email" required={true}/>
  <Input name="password" type="password" placeholder="Password / Blank"
    required={true} minLength={5}/>
  <Button onClick={this.handleLogin} type="#" bsStyle="success">
    Log in / Recover Pass
  </Button>
</Form>

When the button is clicked, this.handleLogin will be called with an object supplying the email and password. The snippet above is everything — there’s no additional javascript lurking behind the scenes.

Here’s the before picture:


<form id="login-form" class="form-inline" role="form" action="#">
  <div class="form-group">
    <input id="login-email"
       class="email form-control input-md"
       type="email"
       placeholder="Email">
  </div>
  <div class="form-group">
    <input id="login-password"
      class="form-control input-md"
      type="password"
      placeholder="Password / Blank">
  </div>
  <div class="form-group">
    <button id="login-button" type="submit"
      class="btn btn-success btn-md">
      Log in / Recover Pass
   </button>
  </div>
</form>

Usually you would then attach an event listener in your javascript code, referring to the login button’s ID string, and whatever bits of validation you want to do client side (Bootstrap has some useful stuff for that).

There’s nothing really wrong with the Bootstrap version, but I find the React.js version much easier to read, and the fact that it’s less repetitive should make it easier to update.

Here is my forms wrapper. I think it probably still needs some work — I’m not sure whether the way I’m handling the form values is fully idiomatic React. I’ve only wrapped the Input type so-far, but I don’t see why the others should be difficult.



var Input = React.createClass({
    propTypes: {
        name: React.PropTypes.string.isRequired,
        type: React.PropTypes.oneOf(INPUT_TYPES).isRequired,
        placeholder: React.PropTypes.string,
        label: React.PropTypes.string,
        required: React.PropTypes.bool,
        oneOf: React.PropTypes.array,
        minLength: React.PropTypes.int
    },

    getInitialState: function() { return {}; },

    getValue: function() {
        return this.refs.input.getDOMNode().value;
    },

    renderInput: function(){
        var className = "form-control input-md";
        return <input type={this.props.type} className={className}
            placeholder={this.props.placeholder} ref="input"/>;
    },

    renderLabel: function(){
        return <label>{this.props.label}</label> ? this.props.label : undefined;
    },

    render: function(){
        var className = "form-group";
        if (this.state.error)
            className += ' has-error';
        return (
            <div className={className} onBlur={this.onBlur} onFocus={this.onFocus}>
                {this.renderInput()}
                {this.renderLabel()}
            </div>
        );
    },

    onBlur: function(e){
        var value = this.getValue();
        var error;
        if (this.props.required && !value)
            error = 'required';
        else if (this.props.oneOf && !(value in this.props.oneOf))
            error = 'oneOf';
        else if (this.props.minLength && value.length < this.props.minLength)
            error = 'minLength';
        this.setState({error: error});
    },

    onFocus: function(e) {
        this.setState({error: false});
        e.stopPropagation();
    }
});


var Form = React.createClass({
    mixins: [BootStrapMixin],

    propTypes: {
        callback: React.PropTypes.func.isRequired,
    },

    getDefaultProps: function() {
        return {
            bsClass: "form"
        }; 
    },

    onSubmit: function(e) {
        e.preventDefault();
        if (e.target.type == 'submit')
            this.props.callback(this.getValues());
    },

    render: function(){
        var className = this.extendClassName();
        return this.transferPropsTo(
            <form onClick={this.onSubmit} className={className} role="form" action="#">
                {this.props.children}
            </form>
        );
    },

    getValues: function() {
        var values= {errors: {}};
        var err;
        jQuery.each(this.props.children, function(idx, child){
            if (child && child.props.name) {
                values[child.props.name] = child.getValue();
                err = child.state.error;
                if (err)
                    values.errors[child.props.name] = err;
            }
        });
        return values;
    },
});

2 thoughts on “Bootstrap forms using React.js

  1. Nimesh (@nimgrg)

    Thanks for the blog post. Was pretty straight forward to follow through. Been through a few ways of doing form validation on React and your way fitted what I wanted to do, easy and simple.

    Reply

Leave a comment