Recap of the state management history in React
This post was originally a note on my Digital Garden 🌱
Disclaimer: this is wildly inaccurate, I’m just writing it down from memory as a recap of where we came from to better understand where we are at today and what could come next.
When React was first introduced it was advertised as the V in MVC. For those that don’t know, MVC stands for Model-View-Controller, in other words, React’s initial concern was only about the view.
These were the days of
createClass. See React Without ES6 if you have no idea what I’m talking about.
People were using React as the view layer on top of other frameworks like Backbone or even Angular.js 🤪 My guess is even Facebook had no idea what to do about state in React those days.
Two way data binding and DOM mutations were the way people managed state updates at the time.
Then came Flux.
Application Architecture for Building User Interfaces
Yay we were saved! There was finally a way to manage state for React applications that made sense.
The problem with Flux is at the moment it was only an architecture pattern, there was no library. You had to build your own. I remember using bloody-flux for an small side project. Which turns out is still up an running on GitHub Pages: http://gillchristian.github.io/patriarchs-timeline.
Time travel 🚀
Then Dan Abramov introduced Redux on react-Europe 2015 🤯
Redux took some really cool ideas and brought them to React. And together came the possibility of fancy tooling. I remember the first time I tried time travel debugging, it was so cool! Your Angular.js application couldn’t do that!
Around the same time some other state management alternatives, like MobX, started to show up.
Redux itself became a whole thing, it inspired other frameworks like Vue and Angular to adopt similar patterns.
Since it was a relatively low level tool, just like React, that gave a lot of freedom to the user to find different ways to make it work for them. In particular when it came to async operations Redux itself gave no official solution. The most popular options are redux-thunk, redux-observable, and redux-saga. There were probably a dozen more options out there.
The problem with Redux was how verbose it could get to have all your state in there. And messing up the performance of the application was relatively easy as well.
GraphQL as state management?
Then another technology from Facebook showed up and became popular. The hype was real, GraphQL would save us! From the perfectly fine and working RESTful approach to developing APIs that is.
With the GraphQL revolution people realized that for plenty of applications data fetching is so coupled with state management that it was probably a good idea to marry the two into a single
abomination solution. And thus Apollo and Relay showed up.
GraphQL never seemed like a great thing to me, probably because I never faced the problems it’s meant to solve. And so I never got into Apollo or Relay, so that’s all I can say. ¯\_(ツ)_/¯
Hook me up, would ya?
Up until this point people would swear by the separation between presentational and container components, also called dumb and smart components.
The idea was that some components should care only about the presentation and others about the logic. Presentational components would not have any kind of logic or state and concern not with the model of the application, after all they were truly dumb. Container components, on the other hand, were the smart ones, all the state, interactions with the model, subscription to Redux, etc. would happen there and they would just render a single presentational component.
In other words, separation of concerns. Problem was people took it very seriously, I’ve had PR rejected and blocked because a component that was rendering a piece of UI also had some local state in it, say a toggle, that had no effect in the rest of the application. As you can see I’m still salty about it 😂
At this point hooks showed up and the line started to blur, a lot, to the point there was no line anymore. Hooks made it super easy to reuse logic and any component that needed that logic could use it. There’s still a need for highly reusable presentational components, of course, but nobody talks about containers anymore.
With the introduction of hooks and the PTSD people suffered from how verbose Redux can become devs started to write more and more local state.
There was no need to convert a functional component to a class to add state, that, I think, is what made the whole difference. There was no friction, only a
useState away from it.
However local state is not a silver bullet. Nothing is, besides actual silver bullets, those used to kill a werewolf or witch.
The problem with local state, whether it is with hooks or not, is that whenever another part of the application that isn’t down the three where the state is defined needs that piece of state it forces the state to be moved up to a common parent.
It is not possible for
X to access the state defined in
Instead we have to move the state up to a common parent.
Do this process several times and you are suddenly longing for Redux again.
Context does solve this problem, but just like Flux, it’s a very low level solution and defining context by hand tends to be messy and verbose. Ain’t nobody got time for that!
This is when solutions like Recoil and Jotai come into play. The elevator pitch is: imagine if
useState was global.
By defining this atom data structure one is able to share state between different parts of the component three without the need to lift state up to the common parent.
Boom 💥 any component can access the same state!
If I’m not mistaken these ideas of atoms come from Clojure.
State is a job for the
Remember coupling the state management and data fetching layers together? Turns out that we don’t need GraphQL for that.
Libraries like react-query and SWR offer a very thin layer around data fetching that is able to manage your state as well. Or at least the state that has to be fetched from some remote location.
This approach can be seen as an inversion of control, instead of having the state management layer take care of the data fetching in some way or another, we now have the data fetching layer taking care of the state.
By leveraging caching strategies, like
state-while-revalidate (that’s where SWR name comes from), this libraries can return (stale) data quickly and swap it for the fresh one once the new request resolves.
From SWR docs:
With SWR, components will get a stream of data updates constantly and automatically. And the UI will be always fast and reactive.
From react-query docs:
Fetch, cache and update data in your React and React Native applications all without touching any "global state".
React Query […] in more technical terms, it makes fetching, caching, synchronizing and updating server state in your React applications a breeze.
I don’t know what interesting, revolutionary, and wheel-reinventing pattern will become popular next. But all we can do is wait and be ready to rewrite our applications when that happens.
Or, maybe, come up with another pattern ourselves to speed up the process 😎