Component-based state management for React

When I ask people what their favorite part of React is, one of the most frequent answers is “Components”.

And I agree. React’s component system is magnificent. It lets you split your code into small, single-purpose parts. It lets you work on one part of the app, while your colleagues focus on something else. It makes it simple to share useful parts with your team, and with a huge number of frontend developers all over the world.

But while React’s component system is great for creating view components, it can’t solve everything. And in particular, it can’t solve state.

Three types of state

React components are great at handling view state, like animations and transitions. And that’s no accident. After all, each React Component is tied at some level to the DOM, which is an abstraction for your application’s view.

But not all state is view state.

This is part of why Redux is so popular. It recognises that not all state is view state. It gives you somewhere to put what I call environment state, or information that must be accessible throughout your entire appliaction.

Examples of environment state include:

  • cached data
  • in-progress http requests
  • authentication

But even then, some state just doesn’t fit a Redux store.

This is because there is a third type of state, that isn’t global, and isn’t tied to the DOM. I call it control state.

As an example, consider a sign-up form component. You may want to re-use this component in multiple places. However, you may also want the state persist even when the component is not in the DOM. The state isn’t view state, and it isn’t environment state — it’s control state.

Examples of control state include:

  • form state
  • errors from POST requests
  • filters and sort orders
  • selected items

In today’s React apps, control state is often lifted up and treated as view state, or forced into a global Redux store. But while this kind of works, it feels like a missed opportunity. The thing is, React is all about components. And if you can create a <SignUpForm> view component, why can’t you create a store component too?

Store components

The thing about state management code is that it is often easily reusable. The problem is that React components are tied to the DOM, while control state is independent from the DOM.

So let’s take a step back for a moment, and imagine what a store component might look like if it wasn’t tied to the DOM.

To start, a store component would probably publish raw objects instead of rendering state, so it would have a publish() method in place of render(). It could still have state and props properties, and follow patterns we’re familiar with from React components.

Say we wanted to create a store component to represent the state of a single form field — perhaps it would look something like this:

class FieldModel extends Component {
  static defaultProps = {
    defaultValue: ''
  }

  constructor(props) {
    super(props)

    this.state = {
      value: props.defaultValue,
    }
  }

  // The only change is that control components publish objects instead of
  // rendering DOM nodes.
  publish() {
    let value = this.state.value
    let error = this.props.validate ? this.props.validate(value) : null

    return {
      value: this.state.value,
      error: error,
      change: this.change,
    }
  }

  change = (newValue) => {
    this.setState({
      value: newValue,
    })
  }
}

Actually, as you may have suspected, this component is real code. You can see it live on CodeSandbox. The only trick is that it isn’t a React component. It’s a Govern component.

Introducing Govern

Govern is a library for managing state with store components. These are a lot like React components – they can receive props, call setState, and define lifecycle methods. They can be defined as classes, or as stateless functions.

But importantly, store components publish raw JavaScript, so they’re not tied to the DOM.

Store components can be used to handle your app’s environment and control state, including actions, side effects and selectors. This means that Govern can replace redux (or MobX), redux-thunk, redux-saga, reselect, and even recompose.

But Govern works great with existing tools too. For example, this gist demonstrates a simple component that lets you interact with your existing Redux store from Govern.

And the best thing? If you know React, then you already know most of Govern’s API, so you’ll be productive in no time.

The best way to get started is to walk through my guide to React forms with Govern. Then, you can try it out live on CodeSandbox, or get more details from the API Reference.

And if you like what you see or find any issues, let me know with a star or an issue on the GitHub project!

One last thing – I’m using Govern in my own apps, so I’ll be keeping you up to date with my experiences over the coming months. If you haven’t already, make sure you stay in the loop by joining my newsletter. I’ll even throw in a few PDF cheatsheets when you do:

I will send you useful articles, cheatsheets and code.

I won't send you useless inbox filler. No spam, ever.
Unsubscribe at any time.

Thanks for reading, and until next time, happy Governing!

Leave a Reply

Your email address will not be published.