For years, in the world of JavaScript UI programming, it’s been normal to divide our application state among several components, with each component owning its own local state. With the architectures of the past this was essentially unavoidable, but the rise of React and declarative rendering has opened the door for a more powerful paradigm to take hold: the single state atom.

Now that more and more JavaScript libraries are abandoning local component state in favor of the single state atom, certain questions have begun to arise. Can we control which portions of the single state atom nested components can access? Is it possible to make reusable components without giving them ownership over their own state? Could we implement an entire application like this using React 0.14’s functional stateless components?

Perhaps most importantly: now that we have a strong ecosystem that can support a single state atom, has local component state gone from a best practice to an alluring trap?

Sources of Truth

How many databases does a typical Web App use to store its application state? Almost always the answer is “exactly one.” Many employ special-purpose databases for queuing and caching (e.g. Redis, Memcache), but relatively few Web Apps grow to the point where it becomes appealing to divide application state across multiple databases.

There’s no technical barrier to carving up a database, so what if we did? Let’s suppose we put our user account information in one MySQL database, their roles and permissions in a second MySQL database, and recent activity in a third MySQL database.

Doing this would have some immediate upsides. We could be certain that the data for each concern was not mixing with the other concerns. When writing queries for each, they would necessarily be small and simple, because it would take extra coordination effort to loop in the others. There’s a certain conceptual appeal to this strict separation.

However, there would also be drawbacks. For one, it would introduce synchronization bugs. What happens if we delete a user account right when something else is querying that user’s recent activity from the other database? Someone will get back some crazy data unless we can make that update an atomic transaction—which would be trivial if those values lived in the same database, but which would be more difficult and error-prone with the state distributed like this.

Another drawback is that backups get way harder. Instead of backing up a single database and being able to restore it on a whim if things go wrong, we would instead have a pile of databases to back up and restore. What if we correctly restore almost all of them, but mess one up? Now our application is in a crazy state – and one we may not realize is crazy until it’s hung around long enough for nonsensical data to propagate and create more broken data.

These problems stem from the fact that we’ve taken data that is innately coupled from a business perspective—user accounts, their permissions, and their recent activity—and decoupled it architecturally. Whether we like it or not, these pieces of data depend on one another, and dividing up their storage will make it harder to work with them in tandem.

We encounter similar challenges in front-end programming when each component has its own local state. We all know “Undo/Redo” makes for a nice UX, but just like “backup/restore,” it’s difficult and error-prone to implement when your state is spread across multiple sources of truth. It’s also easier for components to incorrectly synchronize with one another, leading to crazy application states such as the infuriating message notification component that displays “1 unread message” even as the message list component insists that all messages have been read.

A single source of truth eliminates these synchronization problems at the source. The rise of immutable data and declarative rendering, popularized by React, has set the industry on a path to getting rid of these problems once and for all. More and more front-end architectures are embracing the notion of representing application state as an atomic single source of truth, and are seeing the corresponding benefits that back-end databases have enjoyed for years.

Although the benefits of a single state atom are becoming clearer as more and more front-end architectures move in that direction, there are still questions in the community around how to accomplish familiar tasks in this new world. How can we maintain the conceptual benefit of local component state—the fine-grained control over which pieces of code can access which parts of state—when state has only one owner? And how do we build reusable components without giving each component ownership over its own state?

As with many new technologies, the answer turns out to be surprisingly simple.

Reusable Stateless Components

Consider an accordion widget. When the user clicks a particular section, that section should expand if it was collapsed, and collapse if it was expanded.

Clearly there is state involved there. We need to record which sections are expanded and collapsed—in order to tell how to render the accordion, as well as what to do when a user clicks a given section. How do we do this without giving the accordion some local state to call its own?

On the back-end, we see reusable libraries for common state-based tasks like user authentication. However, these libraries do not come prepackaged with their own MySQL databases; instead, they have an API which allows the application to use them in conjunction with an existing database. The application can store the user data however it pleases, because the authentication library is designed to delegate state storage rather than prepackaging it.

This idea works just as well on the front-end.

Our accordion needs state for two things: rendering and handling user actions. It needs to know the current state (namely, which sections are expanded and collapsed) in order to render, and it needs to change that state (namely, changing which sections are expanded and collapsed) when the user clicks.

Historically, the common way to meet these needs was to give it a chunk of local state, have it read from that to determine how to render, and modify it when the user clicks.

With a single atomic state, instead we delegate: the accordion’s API accepts as an argument the expanded/collapsed data necessary to render, as well as state-updating functions (“expandSection(),” “collapseSection(),” etc.) that it can invoke in response to user input. So when the user clicks an accordion section and the accordion wants it to be expanded, it invokes the expandSection() function it was passed, which in turn updates the single state atom as appropriate.

Not only does this delegation approach scale, it provides a greater degree of control over which components can impact which parts of state. Parent components can provide children with “read access” (providing portions of current state as arguments) but not “write access” (providing functions to the child which can update specifically-chosen parts of the single state atom) or vice versa. You can even provide access selectively depending on the current state!

How would this architecture impact what we can do for our end users?

Suppose we want to implement a UX improvement: if the user refreshes the page, accordions are still expanded and collapsed as they were when the user left. We can easily persist the necessary information in localStorage, but how hard is this to implement using local state versus a single state atom?

With local component state, every time the user clicks an accordion, we must remember to update localStorage to persist it. If we forget to, even once, our states will be out of sync, and we could end up in a crazy state where things are displaying in a way that should not be possible through normal user interaction. Those bugs are not fun to track down.

With a single state atom, and an accordion that delegates state ownership to it, this is the easiest thing in the world. Every time our state changes, we save the whole atom to localStorage. Done. It’s never out of sync, and our application is architected such that all we have to do is restore that one atom and all of our UI components Just Work, reusable accordions and all.

This is exactly the approach we took at NoRedInk when we made a reusable accordion in Elm. Its API is designed to delegate state management to a single state atom rather than owning its own local state, giving us the best of both worlds: reusable components and a single state atom.

Example: Stateless Functional Components in React 0.14

In idiomatic React code, most of the components you write will be stateless, simply composing other components…This pattern is designed to encourage the creation of these simple components that should comprise large portions of your apps.

Here is the example of such a component, from that post:

1

2

3

4

5

// A functional component using an ES2015 (ES6) arrow function:

varAquarium=(props)=&gt;{

varfish=getFish(props.species);

return{fish};

};

Let’s run with that example. What if we want to let user flip a light switch on and off within our fish tank? We could implement that using local component state, with each tank owning whether its light was turned on or off, but let’s try delegating to a single state atom instead.

Here we have one traditional React component at the root of the hierarchy, called App. Its job is solely to hold our app’s single state atom, and its children are responsible for updating that state as appropriate.

Note that both the Aquarium and its child Tank components are stateless; you can be sure of this, because they are using 0.14’s stateless functional component style—which does not even support local component state! Nevertheless, the Tank class has no problem updating the application state as necessary, because its parent has provided it with a setLightOn function which allows it to do so selectively.

Here we can see that the conceptual benefit of local state—fine-grained control over which parts of state components are permitted to access—can be achieved by simple function passing instead, without sacrificing the benefits of the single state atom. Tank does not have access to the entire application state; it only knows about its one single tank, and all it can do to affect that tank is to switch its lights on or off.

In short, we have avoided mixing its concerns with the rest of our application state simply by being selective in what we pass it!

This may seem like an unusual way of doing things, but remember what this gets us: now if we ever want to serialize our entire application state in order to store it somewhere, or to roll back to a previous state, we can do so at the drop of a hat. Making transactional updates across different parts of the application state is now trivial, so race conditions are much easier to avoid.

For more on stateless components, the second edition of “Developing a React Edge” discusses them as well as other changes introduced in React 0.14. I also expect single state atoms to be a recurring theme at the Reactive2015 conference in Bratislava, given the number of talks on technologies based around single state atoms, such as Redux, Om, and Elm.

Takeaways

Despite the conceptual allure of local component state, the benefits of the single state atom are too strong to ignore:

● A single source of truth makes it easier to perform transactional updates and dodge race conditions.
● Serialization for quick persistence and retrieval of state no longer requires a massive coordination effort.
● Debuggers can reliably support time travel across past UI states.
● Undo/Redo becomes a feature whose implementation is easily within reach.

Considering we can use delegation and function passing to build reusable stateless components with even more fine-grained state permissions as what we had with stateful components, even the conceptual benefit of local component state has an easy replacement in the world of the single state atom.

So here’s to the next evolution of front-end development, where we avoid the trap of local component state and start reaping the benefits back-end programmers have enjoyed for years.

You have a list view (a table) on the bottom, and a control panel (between the navbar and the list view). The control panel is a separate component, shared between different view. You can click on some of the icons to switch to other type of views. Well, now, who owns the state? In particular, the number of records is required by the control panel (to display the pager), but the records are required by the list view. Also, the pager can be used to change the records currently viewed, so it can change the state.

In that case, local state to components is a problem. They have to communicate changes by events or some other way, they need to synchronize state, and this is really tricky. This problem is neatly solved by using a single state atom. The problem of who owns the state and how to communicate it to each other component simply disappears.

Jan

Nice post! But I’m still not quite convinced of this. I think the central app state works great for data, but for UI state, that is only used within a single tree, I’m not so sure.

If you have a deeply nested UI, where at a child very deep down in the tree you have a dropdown menu. Would you bother the component at the very top with telling the dropdown if it should be open or closed? In the JSFiddle you abstracted the light state with the knowledge about tanks in the top component. Would you have a state like dropdowns in the top component if there are multiple dropdowns? What if the dropdowns not only live in the same level of the tree, but are spread across levels – what’s a good way of saving that kind of state in a central app state?

Global_1981

Joe

I can there are some advantages to delegating the state to parent components. There may be a need to persist the state of the UI components. But aren’t there disadvantages too? For example – do you want to pollute your application’s top-level state with every detail of every UI component? If you have no requirement to manage that state centrally (store or otherwise), then why add the complexity? Every mouse hover state etc..
Also, what about performance? Centralizing state means each update causes the v-dom diff algorithm to kick in for the entire app. That may be fine on updates, but not so good for 60fps animations that depend on state.

I see the best practice is to make it optional. Many React components do this already. If you set the callbacks then state is delegated to the hosting component, if you don’t then the component manages its own state.

Dave W

I disagree completely. You can have all the benefits that you’re outlining without removing component-level state. The example of the accordion you gave has you pass a function to run when the table is expanded, and when the accordion is collapsed. We already have these with most accordion components, they’re call events. They fire when the component’s state changes. We can then have our app level state update when those are fired if we so choose, and at that time save to localStorage. I’ve done that before, with a jQuery plugin! Long before React. I really fail to see any benefit to not having the component keep track of it’s state. In fact, I see a major problem. You can’t initialize the accordion without needless boilerplate. You now need a variable, and two functions for every accordion you initialize, instead of just an event handler when/if you need to know when it’s state changes.