Simple full-stack data loading for React

React Frontload v2


I'm excited to announce the release of React Frontload v2!

v2 is the first new major version since the original release of React Frontload 3 years ago. It's an evolution of the ideas developed in v1, motivated by practical use in real apps, conversations with the community, and advances in the React ecosystem over that time.

Here I dig into the background and motivation for v2. It should provide some useful context whether you're new to the library or whether you've already used v1.

The headline goal of React Frontload v2 remains the same as v1: simple full-stack data loading in React. But v2 takes a much more opinionated, batteries-included approach than v1, making it both simpler and more powerful. It has a better API and more features, which have been honed and discovered over years of use in real apps. It's much closer to the original vision for the library.

The approach taken in v1 was deliberately targeted, and low level: the core idea was to add the equivalent of an async lifecycle method to React components, in which data could be loaded, and absolutely minimal plumbing to hook it in on client and server.

The reason for this approach was to experiment with the idea in the simplest, smallest-scope implementation I could. Basically when starting out I didn't know if it would be a good idea at all, let alone what patterns would emerge as good ones assuming this functionality were available in React.

Over time, using React Frontload in my own apps, and discussing other peoples' usecases in the community, it turned out the core idea was a good one, and some common questions, patterns and themes did indeed start to emerge:

  1. Since React Frontload is loading data, it'd be really useful and natural for it to provide simple state management out of the box, instead of having to manually hand this off to a dedicated state manager like Redux.
  2. Likewise with metadata around loading state - it'd be really useful for React Frontload to automatically provide this out the box.
  3. React hooks - after the release of hooks, it became obvious they'd provide a more natural interface than the Higher-Order Component (HOC) pattern for doing data loading inline in components. But beyond a simple port of the existing API, hooks also opened possibilities for implementing (1) and (2) in a nice way.
  4. Typescript - the increased popularity of Typescript made it increasingly clear that the API could be better designed for first class integration and better type inference for loaded data. Of course, this would be closely related with all the above.
  5. Particularly early on, there were many misunderstandings and bugs caused by the server side API design. The idea was to abstract away all the details, making server rendering Just Work with a simple wrapper, but this made the implementation much more complex and bug-prone than necessary. This tradeoff was a mistake, especially since server side setup only needs to be done once per app, and only a couple of pretty simple details needed to be exposed to make the implementation much simpler and more robust.
  6. Similarly, the general approach of being low-level and unopinionated meant that not much could be done out of the box for optimisation. Things like avoiding duplicate requests when rendering the same component twice could not be provided automatically, and would have to be implemented manually in your API client. To do so you had to understand how React Frontload worked under the hood, but these details were supposed to be abstracted away. It was the worst of both worlds, and a design mistake.

Despite being aware of all of the above for some time, for a long time I resisted implementing any of them simply because any one of them would require breaking changes to the library.

Since React Frontload handles data loading, it's a foundational piece of the stack in applications that use it. Any breaking change has to be very carefully considered, given how disruptive it will be. As I played around with implementing some of these ideas in turn, though I realised they were good, I never saw enough upside of any particular one to justify this disruption. Even less so the compounding disruption that introducting a few of them in series would introduce.

Even though it had some clear flaws, v1 still seemed good enough - it did its job really well, and was being used happily by a lot of production projects. I valued its stability far more than any one of these new features or better APIs, individully.

But then, while working on an application this last year, which was a classic CRUD app, I decided to fork React Frontload and try something more ambitious: rather than adding one idea at a time, I brought all of them together in a complete rewrite.

What resulted was a lot more than the sum of its parts, and really clicked with me. It ended up being a lot closer to what I had originally envisioned for React Frontload, both in its API but also in its much more opinionated, batteries-included approach.

I realised that since this was a pretty general CRUD app, this would probably work very well for a lot of other people too. It was very different from React Frontload v1, but at least for this usecase, I preferred this implementation a lot to React Frontload v1 and would not have wanted to switch back. Moreover, for future apps, I would have also gone with this new way. I realised I had a candidate for React Frontload v2: something that was actually worth the disruption of a completely new API, new documentation, and a major version change!