App Development

App Development Insight: React State Ownership and Alert Implementation

WillowTree default article featured image

When designing apps (not just web apps) state ownership should always be clearly thought out. Should this button widget know if it’s locked or does the parent decide? Should that modal know if it’s rendered or does some other part of the app decide?

State ownership woes are pretty apparent if you choose React. Our team spent a great deal of time investigating the best methods for carrying out the best implementation, and we learned a few crucial lessons along the way. To demonstrate this, we’ve created a hypothetical alert implementation. If you follow WillowTree’s blog, you may know that we covered alerts in our Backbone Mixin Series. In today’s post, we will revisit this in light of state ownership in a React application.

Before we get started, let’s recap on alerts. Our goal is to present the user with a simple message and a confirm or reject prompt. We will only be presenting the user with this when immediate action is mandatory, so all other aspects of the app should be backgrounded. We want a nice clean API and to keep all aspects of this alert inside a component. The question becomes, “Who decides if the alert is rendered?”

Component Ownership At first glance, it makes sense to put this bit of visibility state on the alert component itself. We can imagine the component looks something like this:

var Alert = React.createClass({
    getInitialState: function() {
        return {visible: false};
    },
    show: function() {
        this.setState({visible: true});
    },
    render: function() {
        if (this.state.visible) {
            // Render as normal
        }
    }
});

Pretty simple. We have our visible state, a show method, and a generic render method.

This is how the parent component would use the alert:

var Parent = React.createClass({
    showAlert: function() {
        this.refs.alert.show();
    },
    render: function() {
        return (
            <div>
                <span onClick={this.showAlert}> Show Alert </span>
                <Alert
                    ref="alert"
                    message="Would you like a boat?"
                    yes="Yes"
                    no="Nope. I can’t swim."/>
            </div>
          );
        }
    });

At a high level, this alert implementation is very elegant. All the state regarding the alert resides in the alert itself, and as a result we can use this component anywhere. Have a simple blog that needs a confirmation prompt? This would work just fine, but when you start having more complex interactions with multiple UI components that need to be synchronized, it pays to have your state in a controller component.

Imagine you have two alerts, one gets displayed when a button is clicked and the other is displayed on a timeout call. Since the knowledge of the alert belongs to the controller component at that time, the state should belong to the controller. With this implementation, we would have a difficult time ensuring multiple alerts do not get rendered at once.

Parent Ownership In an effort to ease synchronizing multiple UI components, we are bubbling up the visible state up to the alert's parent. With that logic gone, alert is basically no more than a render method. Here is what the parent will look like:

var Parent = React.createClass({
    getInitialState: function() {
        return {_alert: false};
    },
    showAlert: function() {
        this.refs.alert.show();
    },
    renderAlert: function() {
        if (this.state._alert) {
            return (
                <Alert
                    ref="alert"
                    message="Would you like a boat?"
                    yes="Yes"
                    no="Nope. I can’t swim." />
           );
        }
    },
    render: function() {
        return (
            <div>
                <span onClick={this.showAlert}> Show Alert </span>
                {this.renderAlert()}
            </div>
        );
    }
});

We simply took the state and the if statement from the alert and moved it over. On one hand, it makes synchronizing two of these components simple. On the other hand though, it makes it very cumbersome to actually use them. Again, why would we actually do this if it makes it a chore to implement? We can clean this code up to make things a bit simpler…

var Parent = React.createClass({
    getInitialState: function() {
        return {_alert: false};
    },
    renderAlert: function() {
        if (this.state._alert) {
            return <Alert {...this.state._alert} />;
        }
    },
    showAlert: function(options) {
        this.setState({_alert: options});
    }
    render: function() {
        // Alert options
        var one = { title: 'title 1', message: 'message' };
        var two = { title: 'title 2', message: 'message' };


        return (
            <div>
                <span onClick={this.showAlert.bind(this, one)}> One </span>
                <span onClick={this.showAlert.bind(this, two)}> Two </span>
                {this.renderAlert()}
            </div>
        );
    }
});

Instead of hardcoding render methods to spit out a predefined alert, we are saving the alert options directly to our state. In our render method, we check to see if we have some alert state available. If we do, we load it into a component and render it to the screen. Now, to show an alert we just set the state and it will get rendered to the screen. By design, it is impossible for the parent to render multiple alerts at once.

A Compromise Is that the end of the story? Having the state owned by the parent gives us more flexibility in coordinating multiple alerts, but man is it ugly. Thanks to the composability of React, we can have the best of both worlds. We can have our state managed outside the child component and still have a clean API to work with. In the case of React Bootstrap, they isolated the visibility state in a wrapper component. Here we will explore an alternate method, using mixins to automate this process for us.

var AlertMixin = {
    getInitialState: function() {
        return {_alert: false};
    },
    renderAlert: function() {
        if (this.state._alert) {
            return <Alert ...this.state._alert />;
        }
    },
    showAlert: function(options) {
        this.setState({_alert: options});
    }
};

If you recall from the previous section, to add flexibility to our parent, we stored alert options in the parent’s state. The mixin we just defined abstracts those specific methods out.

var Parent = React.createClass({
    mixins: [AlertMixin],


    render: function() {
        // Alert options
        var one = {/* title, message, yes, no */};
        var two = {/* title, message, yes, no */};


        return (
            <div>
                <span onClick={this.showAlert.bind(this, one)}> One </span>
                <span onClick={this.showAlert.bind(this, two)}> Two </span>
                {this.renderAlert()}
            </div>
        );
    }
});

With the mixin, we just load it into our parent component and add renderAlert to our render method. We have both a clean API and the flexibility to coordinate our UI easily.

Store Ownership There is one more logical step we can take when building larger Flux-based apps; that is to abstract this UI state into a global store available at the top of the app. When you stop to think, certain UI elements are application specific , not component specific. Components like alerts, lightboxes, and notifications can be triggered from any aspect of your application, and need to coordinate with other existing UI elements to avoid collisions. Having this control within a store has some distinct benefits:

  1. It keeps our components simple. They don’t need to know about collisions.
  2. Rendering is a breeze. Since the business logic coordinating these special elements resides in the stores / actions, our components / controllers are lightweight.
  3. Clean API. There’s no need to have mixins anywhere you want to use a global UI element. Instead, just call a UI action and have your top level controller render it for you. Final Thoughts & Takeaways After this entire journey developing an alert component, what did we learn?

1. State typically belongs to their common ancestor

If you have information that multiple components need to be privy to, find their common ancestor and put it there. Duplicating state leads to more code and information to manage, which translates to more bugs and more headaches.

Always ask yourself, _“Is this feature outside the scope of my current component?”_If yes, perhaps you need a top level controller to take care of it or even a store.

2. Controller views will typically own state of many views

Building on the previous point above; controller components are a common pattern, don’t be afraid of them. When you bubble up state, it needs to go somewhere. That place is typically a controller which orchestrates larger sections of the app.

3. Keep It Simple

When writing components, make sure they are small and to the point. Your components are like legos, it is harder to build with large chunks of bricks that serve multiple purposes. If your components have multiple uses, chances are you should break them into multiple components. It will simplify your code, test cases, and mental overhead.

Quickstart-Guide-to-Kotlin-Multiplatform

A Quick Start Guide to Kotlin Multiplatform

Kotlin Multiplatform, though still experimental, is a great up-and-coming solution...

Read the article