The basic question boils down to "Who should dictate if the alert is rendered?"
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)
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
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?