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;
    },
});

Advertisements

Bootstrap TabbedArea component with React.js

This post is another small note about designing a component library for Twitter Bootstrap using React.js.

Here is how tabbed content regions are created using Bootstrap:

<div>
  <ul class="nav nav-tabs" id="myTab">
    <li class="active"><a href="#home" data-toggle="tab">Home</a></li>
    <li><a href="#profile" data-toggle="tab">Profile</a></li>
    <li><a href="#messages" data-toggle="tab">Messages</a></li>
    <li><a href="#settings" data-toggle="tab">Settings</a></li>
  </ul>

  <div class="tab-content">
    <div class="tab-pane active" id="home">...</div>
    <div class="tab-pane" id="profile">...</div>
    <div class="tab-pane" id="messages">...</div>
    <div class="tab-pane" id="settings">...</div>
  </div>

<script>
  $(function () {
    $('#myTab a:last').tab('show')
  })
</script>

React makes it possible to use the Bootstrap css and supply a much better api:

var MyTabbed = React.createComponent({
  getInitialState: function () {return {activeTab: 0}},
  
  switchTab: function (idx) { this.setState({activeTab: idx}); },

  paneModels: [
    {tabName: "Home", children: [...]},
    {tabName: "Profile", children: [...]},
    {tabName: "Messages", children: [...]},
    {tabName: "Settings", children: [...]}
  ],

  render: function() {
    return (
       <TabbedArea
           paneModels={this.paneModels}
           activeTab={this.state.activeTab}
           switchTab={this.switchTab}
       />);
    }
});

The component library will contain the TabbedArea component, which the parent element will pass an array of objects describing the pane names, their contents, and any other properties we might want to attach. Instead of telling the tabs how to toggle the panes, we maintain a state variable, and supply a callback that manipulates it. It’s taken me a while to get used to this way of designing things, but once it clicks and you use React as it’s intended, the code can be very simple.

Bootstrap has over 60,000 users according to GitHub, and I think the React-wrapped components will be significantly better. So I’m quite excited about this project. The advantage of the React version is not just in the lines of code. The parallel tabs and panes lists in the Bootstrap markup will be much more difficult to manipulate programmatically, and the code that does so will live in some random part of the javascript, separated from the objects it is acting upon, even though the two are entirely coupled. The React version is simply better code.

Here is how I’ve implemented TabbedArea. There are still various styling classes that need to be exposed as parameters, like the option to justify the header tabs, or to use tab styling instead of pills. And maybe there’s another design, which allows us to ship a better api still. I don’t see why not. Any ideas?



var TabbedArea = React.createClass({
    propTypes: {
        // Array of POJO objects for panes
        // Each model should have:
        // classes: object, tabName: string, children: array of objects
        paneModels: React.PropTypes.array.isRequired,
        // The index of the currently active tab
        activeTab: React.PropTypes.number.isRequired,
        // Callback for when a tab is switched.
        // Expects one argument: index of new tab.
        switchTab: React.PropTypes.func.isRequired
    },

    handleClick: function(idx, e) {
        e.preventDefault();
        this.props.switchTab(idx);
    },

    render: function() {
        return this.transferPropsTo(
            <div>
                <ul className="nav nav-pills nav-justified">
                    {this.renderTabs()}
                </ul>
                <div className="tab-content">
                    {this.renderPanes()}
                </div>
            </div>
        );
    },

    renderTabs: function() {
        return this.props.paneModels.map(function(panePojo, idx) {
            return (
                <Tab key={idx} name={panePojo.tabName} 
                    // Note that React handles onClick itself. This isn't
                    // a raw, DOM-attached event handler.
                    onClick={this.handleClick.bind(this, idx)}
                    isActive={idx === this.props.activeTab}
                />
            );
        }.bind(this));
    },

    renderPanes: function() {
        return this.props.paneModels.map(function(paneModel, idx) {
            paneModel.classes['tab-pane'] = true;
            paneModel.classes.active = idx === this.props.activeTab;
            // Build classname string out of all truthy classes
            return (<div key={idx} className={React.addons.classSet(paneModel.classes)}>
                        {paneModel.children}
                    </div>);
        }.bind(this));
    },
});


var Tab = React.createClass({
    propTypes: {
        isActive: React.PropTypes.bool.isRequired,
        onClick: React.PropTypes.func.isRequired
    },

    render: function() {
        var className = React.addons.classSet({active: this.props.isActive})
        return (<li className={className} onClick={this.props.onClick}>
                    <a href="#">{this.props.name}</a>
                </li>);
    }
});

Popover mixin

When I use Bootstrap normally, my popovers reference DOM nodes, since that’s the easiest way to supply them with html-formatted content. I spent a while trying to get that to work, but there’s really no need in React.js — when you have full javascript, it seems easy to have the content passed in as a string, which it can be the parent’s responsibility to supply.

So, all we need to do is write a mixin, like this:

// The hasPopover attribute is read by the controller, described below.
// It's written into the className string by the ClassNameHelper.
var PopoverMixin = {
    getDefaultProps: function(){
        return {hasPopover: true};;
    },

    componentDidMount: function() {
        $(this.getDOMNode()).popover({
            content: this.props.payload,
            animation: this.props.animation,
            placement: this.props.placement,
            title: this.props.title,
            trigger: 'click',
            html: true,
        });
    }
};

Because Bootstrap supplies a click-to-trigger property, we don’t even need to ask the client to bind an onClick method. The popover behaviour is entirely encapsulated in the mixin, with one exception. We also want to disable the popover, and we want to do that by clicking anywhere. We go one better than Bootstrap here. The default behaviour in Bootstrap requires popovers to be disabled by clicking their container button, which is quite nasty. We can implement this feature by listening for events on the controller class:

var Controller = React.createClass({
    // Bind this to onClick
    managePopover: function(e) {
        // This enforces one popover open at once. When you click anywhere,
        // the popover closes; if you click another popover link, that one will
        // open.
        if (this.state.activePopover) {
            this.state.activePopover.popover('hide');
            this.setState({activePopover: null});
        }
        if ($(e.target).hasClass('hasPopover')) {
            this.setState({activePopover: $(e.target)});
        }
    },
   
    getInitialState: function() {
        // State: answers, currentToken, doc
        return {activePopover: null};  
    },
});

The implementation of that feature is very ugly in the old version of my app. I have this bit of setup code:

    $(':not(#anything)').on('click', (e) ->
        $('.popover-link.active').each () ->
            #the 'is' for buttons that trigger popups
            #the 'has' for icons and other elements within a button that triggers a popup
            if (not $(this).is(e.target) and $(this).has(e.target).length is 0 and $('.popover').has(e.target).length is 0) 
                $(this).popover('hide')
                return
        # Special-case for having a different target and trigger. Shrug.
        if not $('#login-button').is(e.target)
            $('#login-password').popover('hide')
            $('#login-form').popover('hide')
    )

That’s pasted verbatim — including the comment! I got it out of some Stackoverflow thread, after searching a long time. I think this was probably the blog post. Check out the Google results for
“Bootstrap close popover”. Clearly I’m not the only one who struggled with this. The event system on React is a nice feature; we get the right thing immediately here.

A mixin-based design for a React.js Bootstrap component library

Bootstrap ships classes for cross-cutting concerns, e.g. size (lg, md, sm) and style (default, primary, success, etc).

One soluton for a React.js component library is to make components for all combinations. Here’s a suggestion for composing the classes via mixins.

The main idea is to have a set of properties, e.g. bsSize, bsStyle, and construct the className string from them:


var ClassNameHelper = {
    extendClassName: function() {
        var bsClass = this.props.bsClass;
        var bsStyle = this.props.bsStyle;
        var bsSize = this.props.bsSize;
        var prefix = this.props.bsClass + '-';

        var classes = {}
        // TODO: Check there are no conflicting values in this.props.className
        if(this.props.bsClass)
            classes[this.props.bsClass] = true;
        if(this.props.bsSize)
            classes[prefix + this.props.bsSize] = true;
        if(this.props.bsStyle)
            classes[prefix + this.props.bsStyle] = true;
        // Merge with previous classes
        if(this.props.className)
            this.props.className.split(' ').map(
                function(klass){
                    classes[klass] = true;
                }
            );
        // This produces a ' ' delineated string
        // from all members of classes that evaluate as truthy
        return React.addons.classSet(classes);
    },
};

The mixin would be used in the component library like this:

var Button = React.createClass({
    mixins = [ClassNameHelper],
    getDefaultProps: function() {return {bsClass: 'btn'};},
    render: function() {
        return this.transferPropsTo(
            <button className={this.extendClassName()}>
                {this.props.children}
            </button>
        );
    }
});

Users could pass in the arguments as props:

<Button bsSize="sm" bsStyle="default">Sign Up!</Button>

I prefer creating ad hoc asset components though, so I think there should also be a set of mixins:

var Large = {getDefaultProps: function() { return {bsSize: 'lg'};}};
var Medium = {getDefaultProps: function() { return {bsSize: 'md'};}};
var Small = {getDefaultProps: function() { return {bsSize: 'sm'};}};

var Default = {getDefaultProps: function() { return {bsStyle: 'default'};}};
var Primary = {getDefaultProps: function() { return {bsStyle: 'primary'};}};
var Success = {getDefaultProps: function() { return {bsStyle: 'success'};}};
var Info = {getDefaultProps: function() { return {bsStyle: 'info'};}};
var Warning = {getDefaultProps: function() { return {bsStyle: 'warning'};}};
var Danger = {getDefaultProps: function() { return {bsStyle: 'danger'};}};
var Link = {getDefaultProps: function() { return {bsStyle: 'link'};}};

var SignupButton = React.createClass({
    mixins = [Large, Success, Button],

    render: function() {return <Button>Sign Up Now!</Button>;}
});

If we can get the design right, I think that a good Bootstrap component library could be a very popular resource.

Better Bootstrap modals and popover with React.js

Updated snippet at the end of the post, via a tip from Steve Hunt.

I’m pretty new to frontend development (I do NLP research), but the way Bootstrap worked always bugged me. I think React offers a way to make things a lot cleaner, but I’m not sure whether the solutions I’m coming up with are crazy. This post is a quick note to get feedback from the React community.

Bootstrap modals

Let’s consider a basic modal use-case. You want to have a button that triggers a modal.

In Twitter Bootstrap, you’ll write two elements:

  1. The payload: a div with the modal window, which will be hidden at first;
  2. The trigger: a link to activate the modal, like a button.
  3. The trigger can be linked to the payload by setting a data attribute on the trigger element, with a css selector identifying it. Alternatively, you can do it via event handling in javascript, catching the onClick method, fetching the payload element, and calling payload.modal(‘show’).

    You also need to do some initialization, either by setting data attributes on the payload element, or by calling a javascript function and passing in parameters.

    What I hate about this

    My beef with this set-up is that I need to manage the linkage between the trigger and the payload. Usually, this means adding an id to the payload element, and referencing this id in the trigger’s data-target attribute.

    React.js components

    Facebook’s React.js lets you define custom components, which encapsulate html, javascript and css properties in one place. This is a big win over traditional templating imo, because often javascript behaviours and html structures are inherently coupled, so they ought to be defined in the same place. An example component for the payload div, written in jsx syntax:

    
    var ModalPayload = React.createClass({
        componentDidMount: function() {
            // These can be configured via options; this is just a demo
            $(this.getDOMNode()).modal({background: true, keyboard: true, show: false});
        },
    
        componentWillUnmount: function() {
            $(this.getDOMNode()).off('hidden', this.handleHidden);
        },
        render: getDefaultProps() {
            return {Header: React.DOM.div, Body: React.DOM.div, Footer: React.DOM.div};
        }
        render: function() {
            var Header = this.props.header;
            var Body = this.props.body;
            var Footer = this.props.footer;
            return (
                <div className="modal fade" role="dialog" aria-hidden="true" data-modalID={this.props.modalID}>
                    <div className="modal-dialog">
                        <div className="modal-content">
                            <Header className="modal-header"/>
                            <Body className="modal-body"/>
                            <Footer className="modal-footer"/>
                        </div>
                    </div>
                </div>
            );
        }
    });
    

    The idea is to provide a reusable wrapper around Twitter’s modals, handling their quirky mark-up structures and invocation calls. Client code would define their modal’s header, body and footer content as once-off assets, and use them as arguments to the ModalPayload component, which wraps them. Example client usage:

    <ModalPayload header={MyHeaderAsset} body={MyBodyAsset}/>
    

    Here the footer will be filled by an empty div. So far so good.

    But, how do we write our trigger? The obvious solution is to have a ModalTrigger component, which we pass in a matching modalID string, which would communicate with the payload via the DOM. That doesn’t really seem like how React is expecting me to do business, though, and I hate writing matching ID strings like that.

    Linking via the DOM

    It would please my black heart if I could write a single component which would own both the trigger and the payload:

        <ModalTrigger trigger={MyTriggerButton} header={MyHeader} body={MyBody} footer={MyFooter}/>
    

    The problem is that funny things happen if the payload element is a sibling of the trigger in the DOM. The standard practice is to put the payloads at the end of the body, to be unhidden and re-positioned when the trigger is fired.

    So: I want to write a component which “owns” an element that won’t be written as a child of it in the DOM. It turns out I had to fight React pretty hard to make this happen, which suggests it’s not a good idea. I did come up with a solution, though. My black heart is not yet fully pleased, but I’d say I’m about 90%.

    What I do is pass in a callback that tells the ModalTrigger component where to the payload:

    
    var ModalTrigger = React.createClass({
        handleClick: function(e) {
            $('[data-modal=' + this.props.modalID + ']').modal();
        },
        render: function() {
            var Trigger = this.props.trigger;
            return (<div onClick={this.handleClick}><Trigger/></div>);
        },
        componentDidMount: function() {
            this.props.renderTo(<Modal modalID={this.props.modalID} body={this.props.body}></Modal>);
        }
    });
    

    The docs indicate that render() should have no side-effects, and that this logic should be handled in componentDidMount.

    We still communicate between the trigger and the payload via the DOM, but at least now the arbitrary ID string is only defined once. I tried using refs, but this doesn’t seem to work for elements that don’t have parent/child relations.

    The renderTo callback is managed by the top-level component, which is my app’s controller:

    var Controller = React.createComponent({
        renderHidden: function(component) {
            var hidden = this.refs.hiddenNodes;
            hidden.setState({
                childNodes: hidden.state.childNodes.concat(hidden.childNodes, [component])
            });
        }
    
       render: function() {
         <div className="container-fluid">
            <HiddenNodes ref="hiddenNodes">
            </HiddenNodes>
            <div>
               <h1>Hello possums</h1>
               <div>
                  <ModalTrigger trigger={MyTriggerAsset} body={MyBodyAsset} modalID={"arbitraryString"} renderTo={this.renderHidden}/>
               </div>
            </div>
         </div>
       }
    });
    
    var HiddenNodes = React.createClass({
        getInitialState: function() {
            return {childNodes: []};
        },
        
        render: function() {
          return (<div>{this.state.childNodes}</div>);
        }
    });
    

    I think a mixin could handle this boiler-plate for the controller.

    So. There are surely better ways. What should I do? Just, “don’t want this”? It’s a lot of trouble to go to avoid passing around ID strings. But the component should be quite reusable. Getting rid of that for good is worth some deviousness, in my opinion.

    No matter what the eventual solution looks like, I think publishing nice React components for Twitter Bootstrap modals, popovers and tooltips is a good idea. These receive a high volume of questions on stackoverflow, and involve a lot of fiddly boilerplate. I’ve only been playing with React for a day or so now, but it seems clear that it offers a better way.

    UPDATE: After talking to Steve Hunt on IRC (#reactjs on freenode is very responsive — you should join!), I was led to a much much simpler solution. Steve asked me what the problem was with having the payload as a sibling of the trigger on the DOM, so I took another look and found a very simple fix — it was just that the click handler on the modal would bubble events back up to the trigger. Easy: e.stopPropagation.

    So this works pretty well:

    
    var ModalTrigger = React.createClass({
        handleClick: function(e) {
            $(this.refs.payload.getDOMNode()).modal();
        },
        render: function() {
            var Trigger = this.props.trigger;
            return (<div onClick={this.handleClick}>
                <Trigger/>
                <Modal ref="payload"
                    header={this.props.header}
                    body={this.props.body}
                    footer={this.props.footer}>
                </Modal>;
            </div>);
        },
    });
    
    var Modal = React.createClass({
        componentDidMount: function() {
            // Initialize the modal, once we have the DOM node
            // TODO: Pass these in via props
            $(this.getDOMNode()).modal({background: true, keyboard: true, show: false});
        },
        componentWillUnmount: function() {
            $(this.getDOMNode()).off('hidden');
        },
        // This was the key fix --- stop events from bubbling
        handleClick: function(e) {
            e.stopPropagation();
        },
        render: function() {
            var Header = this.props.header;
            var Body = this.props.body;
            var Footer = this.props.footer;
            return (
                <div onClick={this.handleClick} className="modal fade" role="dialog" aria-hidden="true">
                    <div className="modal-dialog">
                        <div className="modal-content">
                            <Body className="modal-header"/>
                        </div>
                    </div>
                </div>
            );
        }
    });
    

    Black heart, pleased.