RSS/Milan LajtošGitHub/Milan LajtošYouTube/Milan LajtošBuyMeACoffee/Milan Lajtoš

Finally a State Management (in React) for Dummies

Almost two years ago I had a pretty radical idea – state management in React apps should be easy, not hard. Bonkers, right? With this mindset, I came up with a prototype for next generation state management library. Immediately, the whole React community saw its brilliant design, and everyone migrated their apps (legacy too) to my lib. The world became a better place.

Sadly though, this is not what happened. In reality, nobody gives a shit about new ideas, and more so about new JS libraries. There is too much of both, and everyday there is a new batch, which is mostly just a noise echoed from the day before. However, good ideas don't die – they can reborn much stronger even without any knowledge about the previous incarnations. It seems I stumbled upon one of them.

Classy State

My library was called classy-state, and the main idea was that standalone JS objects are the best abstraction for holding the state even though they don't work in a React world. If we could use plain state object in a React app as-is, it would be a good thing to do - there isn't a fuck-ton of new terminology, you don't need any new tooling, and everything would be as simple as possible. Basically an anti-thesis to Redux.

Enough of words, just look at this code implementing a canonical example – Counter:

import React from "react";
import { useClassyState } from "classy-state";

class Counter {
  state = 0
  increase() { this.state += 1; }
  decrease() { this.state -= 1; }
}

function App() {
  const counter = useClassyState(Counter);
  
  return (
    <>
      <div>Count: {counter.state}</div>
      <button onClick={counter.increase}>+1</button>
      <button onClick={counter.decrease}>-1</button>
    </>
  );
}

You wrap your state in a class Counter that holds the state count and its associated mutations increase & decrease. Single call to the magical useClassyState function will give you a React-compatible instance of Counter class that works as you would expect.

I explored two approaches to implement useClassyState function – naïve approach uses JS magic (prototypes, binding and other shit) and is partially described in the main repo. However, approach using JS proxies was easier to hack together and I knew that more capable person would make it work for every possible use case.

I got a glimpse of the future and I felt happy because I knew someday it will become common. With this feeling I abandoned the project and started to explore other stuff.

Valtio

Today, the last day of 2021, while I was browsing Reddit, I found a next generation library that will redefine state management in React world. Stop me, if this sounds familiar. After reading examples and docs I could not stop smiling – I knew how to use this lib immediately. To test my intuition I coded this familiar example:

import React from "react";
import { proxy, useSnapshot } from "valtio";

class Counter {
  state = 0
  increase() { this.state += 1; }
  decrease() { this.state -= 1; }
}

const counter = proxy(new Counter());

function App() {
  const counterSnapshot = useSnapshot(counter);
  
  return (
    <>
      <div>Count: {counterSnapshot.state}</div>
      <button onClick={counter.increase}>+1</button>
      <button onClick={counter.decrease}>-1</button>
    </>
  );
}

Holy shit! It even works with async and computed stuff – see for yourself. And the best part is that people are really adopting it to create production apps. Finally, a lightweight state management for dummies like me.

The world is indeed a better place now.

What comes next?

Of course, this is not the end. There are many open problems related to state management. A really big one is real-time replicated state sharing across many clients. You know, like a multi-user TODO app that updates immediately as someone changes a thing, but also works offline. That last part is quite tricky. There is a great article Local-first Software at Ink & Switch, which goes into detail on how apps of the future should be built. Luckily, there is SyncedStore, which is doing exactly that.web3

The future seems much more bright collaborative. Happy 2022! 🥳


Notes:

  • MobX – If you are a seasoned React developer, you might object that this is exactly what MobX is doing. At the time of my experiment, MobX 5 provided similar plain-object semantics, but its usage was hindered by the use of non-standard JS (decorators). Later that year came MobX 6 that made a good step in a right direction (no decorators & makeAutoObservable), but vanilla objects can't be used. On top of that, there are new concepts that you have to learn to fully embrace the library. I hope MobX 7 will make another great stride to the platonic ideal of the JS state management. By the way, big shout-out to Michel Weststrate for bringing MobX and Immer to the masses. It is great to see such fresh ideas once in a while.
  • valtio-yjsDaishi Kato, the author of Valtio and Jotai (another state lib that is taking a bottom-up atom-oriented approach to state management, similar to Recoil), mentioned there is valtio-yjs, which is similar to SyncedStore. Both, SyncedStore and valtio-yjs, are using fantastic Yjs for the behind-the-scenes CRDT magic, and they both provide mutation-oriented API. Create a JS object, share it across net without central server, imperatively mutate it however you like on any client, and BOOM it is automagically synced for every participant. If this isn't the holy grail of the Web, I don't know what is.
  • I wrote a sequel to this post called Serverless Collaborative Hierarchical TODO App in 200 LoC