Skip to content

Instantly share code, notes, and snippets.

@kaw2k
Last active August 29, 2015 14:09
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save kaw2k/396184a65a80ae9a867f to your computer and use it in GitHub Desktop.
Save kaw2k/396184a65a80ae9a867f to your computer and use it in GitHub Desktop.
Alert API

Thoughts on alerts

The basic question boils down to "Who should dictate if the alert is rendered?"

Parent controls all

In this situation, what you return from your render method is always rendered to the screen. Let's start by making a hypothetical API

<Alert
    title="Awesome Alert"
    message="Do you like kittens?"
    buttons={{yes: 'Yes', no: 'No'}}
    onClose={function(buttonKeyValue) {}}
    />

We have a simple API. Buttons are key values, where the key is the event name fired and the value is the rendered string. onClose gets fired whenever a button is clicked and passes along the key of the clicked button. It isn't the most flexible API, but it keeps your alert messages consistent.

One key thing to understand is if you render this component it will get rendered to the screen. This component always assumes it is visible, it has no internal state values. Lets look at how we might use it.

// ...
getInitialState: function() {
    return {
        _alert: false
    };
},

showAlert: function(options) {
    var self = this;
    return function() {
        self.setState({_alert: options});
    };
},

hideAlert: fucntion(button) {
    self.setState({_alert: false});
},

renderAlert: function() {
    if (this.state._alert) {
        return <Alert {...this.state._alert} />
    }
},

render: function() {
    var alertOptions = {
        title: 'Title',
        message: 'Message',
        buttons: {yes: 'Yes', no: 'No'},
        onClose: this.hideAlert
    };

    return (
        <div>
            <span onClick={this.showAlert(alertOptions)}>
                Open Alert!
            </span>

            {this.renderAlert()}
        </div>
    );
}
// ...

The usage of this component is pretty verbose. With a simple mixin, we can elimnate almost all this boilerplate code.

var AlertMixin = {
    getInitialState: function() {
        return {
            _alert: false
        };
    },

    _alertOnClose: function(onClose) {
        this.setState({
            _alert: false
        });

        if (onClose) {
            onClose.apply(this, [].slice.call(arguments, 1));
        }
    },

    showAlert: function(options) {
        var self = this;
        var onClose = self._alertOnClose.bind(this, options.onClose);

        return function() {
            this.setState({
                _alert: <Alert {...options} onClose={onClose} />
            });
        };
    },

    renderAlert: function() {
        if (this.state._alert) {
            return <Alert {...this.state.)alert} />
        }
    }
};

// ...
mixins: [AlertMixin],

render: function() {
    var alertOptions = {
        title: 'Title',
        message: 'Message',
        buttons: {yes: 'Yes', no: 'No'},
        onClose: this.hideAlert
    };

    return (
        <div>
            <span onClick={this.showAlert(alertOptions)}>
                Open Alert!
            </span>

            {this.renderAlert()}
        </div>
    );
}
// ...

Slightly better, we abstracted all the ugliness out of the code, however we still have that ugly renderAlert call in our render method. What does this give us? Why would we want the parent to control things?

Pros

  • More control by the parent
  • Can easily be abstracted into a store and have one of your top level "controller" views be in charge of coordinating all incoming alerts.
  • What you render is what you get

Cons

  • More verbose
  • Requires more effort to use it
  • Less "drag and droppable" (again, more effort to use)

Component controls all

The flip side is the parent always renders the component, and the component has internal state to determine if it is rendered or not. Again, let's look at an example API.

<Alert
    ref="oneAlert"
    title="Awesome Alert"
    message="Do you like kittens?"
    buttons={{yes: 'Yes', no: 'No'}}
    onClose={function(buttonKeyValue) {}}
    />

As it turns out, not much has changed, all we did was add a ref tag. Internally we are adding a show method to the instance. We call this method when we want the alert to be visible. Now, how do we use this?

// ...
onClose: function(button) {},

showAlert: function() {
    this.refs.oneAlert.show();
},

render: function() {
    return (
        <div>
            <span onClick={this.showAlert(alertOptions)}>
                Open Alert!
            </span>

            <Alert
                ref="oneAlert"
                title="Awesome Alert"
                message="Do you like kittens?"
                buttons={{yes: 'Yes', no: 'No'}}
                onClose={this.onClose}
                />
        </div>
    );
}
// ...

Much cleaner, no mixins required, no ugly method calls. Why wouldn't we want to use this?

Pros

  • Much cleaner usage, less boilerplate
  • Very compartmentalized, all the state of the alert is tucked into the alert
  • Testable, since everything is contained we can test alerts without needing the parent

Cons

  • What we render is not necessarily what is visible
  • Harder to manage alerts on a project level

Final questions

What this boils down to is a question of who should own state: Should the component control it or the parent? If we have the parent control what is rendered it leads to more boilerplate code, however it makes it easier to control the component accross the entire application. We can have a store manage all the alert options that need to be rendered and only make visible the one that needs to be visible. On the other hand, if we let the component control its state, we have a clean, isolated usage, however things could get tricky managing multiple alerts.

What are your thoughts?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment