React State Management With Unstated

This post was originally published here.

The React community has seen a number of different state management patterns emerge over the last few years. They range from the very straight-forward setState API, to third-party libraries such as Redux and MobX (there are more, but these are the most widely used), and to the recently updated Context API.

Unstated is a fairly new library from @jamiebuilds that’s been gaining some momentum in the community. It leverages the power of React’s Context API to make state management extremely simple. How simple? The tagline of unstated is:

State so simple, it goes without saying.

After a few hours of working with it I realized just how simple it is. The API is relatively small, and you can get up and running with it in a matter of minutes. This is a huge positive in a world where third-party state management solutions usually take considerable time to learn.

State management primer#

Before getting started, let’s discuss why we’re really here in the first place. If you’re reading this post then chances are you’re either not happy with the existing solutions out there or you’re looking to try something different. The former makes for much more interesting banter, so let’s explore it.

React ships with its own internal state management solution, setState. It’s a straightforward API and a perfectly fine solution for most projects. Most, if not all React devs learn and use setState before anything else (as it should be). You will see many respected voices in the React community tell you not to use anything else until it’s absolutely necessary. I would largely agree.

But, eventually you’ll hit some pain points. In my experience, these pain points are usually in the form of large state objects and event handlers muddying up components, along with the annoying “prop drilling” pattern that inevitably emerges when passing state down to child components.

This led us to libraries such as Redux and MobX, which abstract state management away from the component level while still allowing components to “subscribe” to any given piece of state. This mostly solves the problems I mentioned above with setState, so why look any further? These libraries are hugely popular and well maintained. Wouldn’t it make sense to double down on one of them and never look back?

With great power comes great responsibility boilerplate.

I’m sure there’s a larger discussion to be had about the pros and cons of these libraries, but I don’t want to stray too far off our intended path here. I do, however, think it’s important to mention the issue of boilerplate, because it’s a real thing and something that has always frustrated me about Redux in particular.

The code is verbose. The amount of files (depending on project structure) you need to interact with just to make a change to a piece of state can be overwhelming. It’s easy to start a new project with good intentions only to find yourself confused by your own folder structure because there is no standard.

This is what eventually led me to try unstated.

Getting started#

You can install unstated with yarn via yarn add unstated. From there, integrating it into your React application is a breeze. Like I mentioned before, the API is very small, exposing only 3 components:

import { Provider, Subscribe, Container } from 'unstated' 

Provider#

The Provider is very similar to the other Provider types existing in libraries like Redux. You use it at the highest level of your application necessary to provide state to specific components:

<Provider>
  <App />
</Provider>

Container#

The Container is used when creating a “slice” of state. You use it in a similar way you would use React.Component:

class StateContainer extends Container {

  state = {}

  updateThis = () => { this.setState({}) }

  updateThat = () => { this.setState({}) }

}

StateContainer will hold a piece of application state, along with any methods that exist to update that state. It’s a beautiful thing really, because everything is encapsulated together.

Notice the use of setState here in the handler methods. According to the official unstated docs:

setState() in Container mimics React’s setState() method as closely as possible.

This means that calling setState inside the Container will cause subscribed components to re-render! You can even use setState as a function:

this.setState(prevState => ({}))

Subscribe#

Now that you know how to create state, how do you pass it to your components? This is where Subscribe comes in. It uses a render prop to pass state and methods into your components:

<Subscribe to={[StateContainer]}>
  {container => (
    /* access container.state */
    /* access container.updateThis() */
    /* access container.updateThat() */
  )}
</Subscribe>

One interesting point to make about Subscribe is that the to prop takes an array. This gives you the ability to pass multiple state container instances into your components:

<Subscribe to={[StateContainer, OtherStateContainer]}>
{(container, otherContainer) => /* */}
</Subscribe>

An example#

Now that we’ve gone over the entire API (yes, that’s essentially the entire API in a nutshell), let’s walk through an example together, shall we? I’ve gone ahead and created a sample project here. The project uses create-react-app, styled-components 💅🏻 and rcolor to generate new background colors out of random hexadecimal values.

The container#

Inside src/containers/ColorContainer.js you’ll see encapsulated logic to manage the current state of colors:

import { Container } from 'unstated'
import rcolor from 'rcolor'

class ColorContainer extends Container {
  state = {
    color: rcolor()
  }

  make = () => {
    this.setState({
      color: rcolor()
    })
  }

  active = () => this.state.color
}

export default ColorContainer

Everything inside of ColorContainer is only concerned with the state object. Nothing more, nothing less. Now, any component in the application can “subscribe” to this container, rendering based on the current state or updating that state based on user interaction.

The subscriber#

In src/components/App.js you’ll find the application itself:

import React from 'react'
import { Subscribe } from 'unstated'
import ColorContainer from '../containers/ColorContainer'
import { Outer, Inner, Button } from './styled'

const App = () => (
  <Subscribe to={[ColorContainer]}>
    {color => {
      const active = color.active()
      return (
        <Outer bg={active}>
          <Inner>
            <h1>{active}</h1>
            <Button onClick={color.make}>Another one!</Button>
          </Inner>
        </Outer>
      )
    }}
  </Subscribe>
)

export default App

When a user clicks on the rendered button, the make method updates the state of ColorContainer with a new hexadecimal value. This causes any subscribed components to re-render, in this case the App component. App will fetch the new value using the active method defined on ColorContainer and pass it to Outer, a styled component that styles its background property using the bg prop.

The provider#

And finally, src/index.js imports the Provider component from unstated and wraps the entire app inside of it, allowing any child components beneath to access container instances:

import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'unstated'
import App from './components/App'

render(
  <Provider>
    <App />
  </Provider>,
  document.getElementById('root')
)

Conclusion#

In my opinion, the secret sauce behind unstated is how integrated it feels with React itself. It feels like this is how we were meant to manage state in React. Not only that, but it’s footprint is so small that you can sprinkle it in here or there. You don’t need to think about structuring your application to fit this massive paradigm.

I think unstated fits really well in the React ecosystem, sitting nicely between setState and other libraries like Redux and MobX. Consider giving it a try on your next project. You will find that it is a joy to use!