By moving the state up to <app> and giving isOpen a single source of truth, we were able to ensure that <collapsible-panel> and <button> would never fall out of sync.

In the example above, <app> was the only ancestor of both components in our component tree. Say we had a larger app:

In this case, it would be sufficient to move isOpen to <main> to ensure isOpen had a single source of truth.

Now, a definition. Given a set of components, the Lowest Common Ancestor of this set is the component that’s deepest in the UI tree but still above each component in that set.

This definition along with the discussion above leads us to the following general principle:

When dealing with changing application state that lives in your UI hierarchy, store that state in the Lowest Common Ancestor of all components that need it.

Storing shared state on the LCA, and then having that common ancestor pass its state to the relevant children, ensures that the state will have a single source of truth, and that every component that renders from that state will stay in sync.

Note that it can sometimes be convenient to create a new common ancestor component, just for the sake of owning and managing the shared state.

Adding a logout button (pulling app state out of the UI)

In many situations, the principle of LCA is sufficient. State has a single source of truth, and is passed down to all children who need it. The problem you can run into, however, is when the LCA is very far away in your component hierarchy from the relevant children.

In this case, you can end up with many components acting as middlemen. These components must know about and pass along various pieces of shared state to leaf components, making your UI difficult to refactor. There’s even a code smell of the same name in object-oriented design, suggesting that this is indeed a concern.

An example of how this could happen is if many components in your app needed to know about the current user:

In this app, data from the current user affects many components.

Assuming this many nodes need to know about the current user, the LCA would be <app>, and every component would need to pass along currentUser to every other component. This definitely smells like Middle Man.

The solution to this problem is to pull the state out of the UI hierarchy. The idea here is that, while isOpen in our earlier example corresponded directly to a particular onscreen UI element (whether the panel was open), currentUser does not. So, it doesn’t really make sense for <app> (or any other UI element) to “own” currentUser.

In Ember, we can use a Service to solve this problem (in React, you might use a Flux store). A Service is a long-lived data container that exists independent of the UI tree. After we set it up, components can use dependency injection to ask for the data in the Service. Importantly, their parent components are none the wiser.

// app/services/current-user.js

export default Ember.Service.extend({

name: 'Bob',

email: 'bob@acme.com'

}); // app/components/sidebar.js

export default Ember.Component.extend({

currentUser: Ember.inject.service('current-user')

});

Now, <sidebar> can access currentUser in its template:

<h2>{{currentUser.name}}</h2>

<sidebar> has become more self-contained, making it easier to move around in the UI in later refactorings.

Conclusion

So, when should application state exist in the UI hierarchy, and when should it be pulled out? As in most areas of software design, there is no black and white answer. Instead, we must understand the tradeoffs involved, and make decisions on a case-by-case basis.

State that’s stored directly in the UI hierarchy is often easier to understand and requires less boilerplate; but, the more components that need a particular piece of state, the more brittle your UI hierarchy becomes. Eventually, it makes sense to move “popular state” into a UI-independent data container, an identity map which other components can read from.

The key insight is that all changing application state should have a single owner, and thus a single source of truth. Further, only the owner should be allowed to mutate that state. If the state lives in the UI hierarchy, the single owner should be the Lowest Common Ancestor of all components that need that state.

In closing,

Start by storing state on local component instances Once a piece of application state is needed by more than one component, move that state up to the LCA Once there are many components acting as middlemen, or it feels wrong for any particular component to own some state, pull that state out of the UI hierarchy and into a data container (an Ember Service, a Flux store, etc.)

While our example above might seem simple enough, this pattern goes a long way in keeping your application’s architecture consistent and easy to reason about.