State Management Beyond Redux

State Management Beyond Redux

JB Rubinovitz
Co-founder and CEO
· 5 minutes

At Homebase, we think a lot about client-side data. One problem we wonder about is the complexity of state management libraries. Redux was a huge step forward when it came on the scene. It forced us to think about how we mutate and subscribe to changes in our data along with giving us places to put that code. Over time, however, we realized we might have too many places to put that code...

I don’t like Redux very much, but this sounds like the opposite of its problems? With Redux, you do know deterministically where the code lies because you can follow the reducer chain. The problem is that what should’ve been local often becomes global.

— Dan Abramov (@dan_abramov) March 22, 2020

Even the co-creator of Redux, Dan Abramov, sounds ready for the next stage in state management.

Luckily for us, over the past few years programmers have created several new approaches to state management. In this post, we share our research on the trends in post-Redux state management and how we tried to incorporate the best parts of existing libraries into one solution. We hope you find a new library to play with and maybe even use in production.

We went through the most popular state management libraries and analyzed what developers wanted to see in their state management libraries. Below are some trends we got excited about:

Simple API

Ever since Redux, we and developers we've spoken to craved a simpler way to handle state. It makes sense that most state management alternatives we looked at prioritized simplicity. Zustand really shines here, seeming to prioritize a simple API above all else.

1import create from 'zustand'
2
3const useStore = create(set => ({
4  bears: 0,
5  increasePopulation: () => set(state => ({ bears: state.bears + 1 })),
6  removeAllBears: () => set({ bears: 0 })
7}))

A code example from Zustand.

Declarative

Another way to make code simpler is to make it declarative. This is the main selling point of Cerebral. While we are huge fans of simplicity through declarative programming, we found Cerebral more complicated than we'd like.

1import * as React from 'react'
2import { connect } from '@cerebral/react'
3import { state, sequences } from 'cerebral'
4
5export default connect(
6  {
7    inputValue: state`inputValue`,
8    changeInputValue: sequences`changeInputValue`
9  },
10  class MyComponent extends React.Component {
11    componentDidMount() {
12      this.props.reaction(
13        'focusUsername',
14        {
15          error: state`usernameError`
16        },
17        ({ error }) => error && this.input.focus()
18      )
19    }
20    render() {
21      return (
22        <input
23          ref={(node) => {
24            this.input = node
25          }}
26          value={this.props.inputValue}
27          onChange={(event) =>
28            this.props.changeInputValue({ value: event.target.value })
29          }
30        />
31      )
32    }
33  }
34)

A code example from Cerebral.

Hooks

React hooks are another path to simplicity since they eliminate the amount of concepts you need to remember when writing React applications. With the advent of React Hooks, we saw libraries such as Zustand and Unstated start to use hooks for state management.

1function useCounter(initialState = 0) {
2  let [count, setCount] = useState(initialState)
3  let decrement = () => setCount(count - 1)
4  let increment = () => setCount(count + 1)
5  return { count, decrement, increment }
6}
7
8let Counter = createContainer(useCounter)
9
10function CounterDisplay() {
11  let counter = Counter.useContainer()
12  return (
13    <div>
14      <button onClick={counter.decrement}>-</button>
15      <span>{counter.count}</span>
16      <button onClick={counter.increment}>+</button>
17    </div>
18  )
19}

A code example from Unstated.

Server State

One of the developments we're most excited about in the state management space is the acknowledgement of "server state". Both Recoil and React Query try to make it easy to integrate client state with your server state using Asynchronous data queries. The trade-off ends up being added complexity.

1const currentUserNameQuery = selector({
2  key: 'CurrentUserName',
3  get: async ({get}) => {
4    const response = await myDBQuery({
5      userID: get(currentUserIDState),
6    });
7    return response.name;
8  },
9});
10
11function CurrentUserInfo() {
12  const userName = useRecoilValue(currentUserNameQuery);
13  return <div>{userName}</div>;
14}

A code example from Recoil.

Putting it all together

Looking at these trends we are more excited than ever about front-end development and decided to throw our hat in the ring with a state management library of our own. We wanted the simplicity of a library like Zustand with the power of a library like Recoil.

So, in a month we ended up creating a state management library around the local-first database we are working on and voila: very simple, imperative code, but with the functionality of a relational graph database right at your finger tips in the client.

As for "server state", we are working on automatic syncing client and server data via a CRDT.

It's already integrated into several applications, but we'd love to hear your thoughts on Homebase React.

1const Counter = () => {
2  const [counter] = useEntity({ identity: 'counter' })
3  const [transact] = useTransact()
4  return (
5    <div>
6      Count: {counter.get('count')}
7      <div>
8        <button onClick={() => transact([{
9          counter: {
10            id: counter.get('id'),
11            count: counter.get('count') + 1
12          }
13        }])}>
14          Increment
15        </button>
16      </div>
17    </div>
18  )
19}

A code example from Homebase React.

Conclusion

We hope this was helpful! The chart below summarizes what we discussed and does a breakdown of what features each library has prioritized.

State management feature comparison

LibraryClient StateDeclarativeServer stateSimplerHooks
Cerebral
Zustand
Unstated
Recoil
React Query
Redux
Homebase
MobX
Overmind
Freactal

Notes:

  • Redux does have middleware to try to incorporate server state.
  • Unstated has unreleased React hooks.