There was a recent article that made it to the top of hackernews yesterday that I felt was interesting but didn't quite resonate with me.
I wrote an initial response to this post on twitter but feel the need to write a longer post about it:
React is frustrating because it has accumulated a decade of debt, added a ton of complex features and continues to grow in size. It’s a black box that is so complex that it will eventually buckle under its own weight. Most insanely popular libraries share the same trajectory. — Eric Bower (@neurosnap) September 22, 2022
First, all of the reasons for why react is awkward are true.
The main criticisms are:
- Forms are difficult and confusing (when to use controlled vs uncontrolled inputs)
- Context doesn't replace the need for a third-party state management solution
- Refs are awkward to work with
- Tracking dependencies for
useEffectis annoying especially when other competing libraries are able to avoid it.
- Derivatives of
useEffectare nice but indicative of a code smell
- Awkward rules for hooks
- Class components -> functional migration pains
I agree with all of these criticisms, but ultimately they aren't the reasons why I'm starting to dislike react.
You can't be one of the most popular libraries on the planet without becoming a magnet for a million use cases. React has been wildly successful because it has adapted and grown to satisfy the needs of so many businesses. It has worked so well that is has become a complex, black box that is impenetrable to most end-users.
I've noticed a similar trend with many popular libraries over the years. They are never "feature complete" and continue to grow in size until they are so large that people become disillusioned by it and start looking for simpler alternatives.
Keeping popular libraries limited in their feature-set takes discipline that most of us engineers struggle to maintain. Why would we? We love writing code! We want to write code!
the black box
I'm intentional about taking a cursory glance at the libraries I use. I've learned a great deal about programming by taking the time to read library source code. However, react is one of those libraries that is far too complex for me to invest the time. I read the RFCs, I read some of the pull requests, and I read all about fibers when they were released. Now we have hooks, SSR hydration, server components, suspense, concurrent mode, react compiler ... the list keeps growing over time.
Furthermore, since so many people use react, there's a great sense of responsibility to not break backwards compatibility. This is a doubled-edge sword. On one hand, end-developers are able to upgrade react without too much headache. But on the other hand, changes that were proposed years ago are still being implemented.
In the beginning I loved react because the API was powerful and concise. The initial implementation could be written in less than 1000 LoC (I've written it before).
However, it's now an impenetrable fortress and the time investment to learn the internals is not worth it for most end-users. Maybe that's okay, but it's a big reason why I don't like react as much anymore.
The beauty of react can be described by a single function:
const view = func(state)
View is just a function of state. This paradigm shift allowed us to go from an imperative-style, direct DOM manipulation towards a more declarative style. We were able to assume our page was re-rendered on every state change.
This made the code easier to write and read. However, this was a lie. React is not declarative because our functions produce side-effects. From data fetching to event handlers, our react components produce side-effects which transition us from declarative (here's the html to render) to imperative (when an event happens run this function and manipulate state).
React doesn't handle side-effects particularly well and its impurity has lead to a lot of awkward situations that the cited article above articulates.
That's great, erock, but what's the alternative? It's not fair to say something sucks without presenting a better solution.
The solution is to create pure functions that don't produce side-effects at all. The solution must take the entire picture of rendering html without ignoring data fetching, event handlers, state transitions, etc.
Really, what I want, is a solution that generates views based on a stream of events. Think about it, events could be prop changes, state changes, a user clicking a button, fetching data, etc. I don't want a view that is a function of state, rather, a view that is a function of an event!
const view = func(event)
The closest things we have to that today is cycle.js.
Another interesting library that has piqued my interest is crank.js which uses generators to render components. What I really like about crank is that it leverages the power of generators to respond to events and render HTML as a result. You mix cycle.js, generators, and elm's model-view-update paradigm and you might have something interesting.
Vercel and the React team are working too closely together. When I read the announcement about server components and how the react core team was working "closely" with the next.js team to deliver server components first, I felt myself less excited about it.
Why should I be less excited about the most popular view library partnering with the most popular react framework to release server components? Because now next.js is cornering the market and suppressing competition in the space. It is fostering favoritism for one particular framework that not everyone uses or wants to use. I understand the potential value but it really does feel like a conflict of interest. Vercel is profiting from being first-to-market for a new react feature while excluding everyone else from the initial participation. The feature itself is being manipulated by a company that has a vested interest in pushing their cloud platform.
It's hard to articulate why it's a problem, but I really don't like the favoritism here. At the end of the day, maybe it doesn't matter. However, react is part of facebook and next.js is part of vercel. We shouldn't kid ourselves what the ultimate motivation is here. Creating a better library is secondary.
I do still really enjoy react and it's still my recommendation for building web apps. In my free time, however, I'm looking at alternatives. I'm starting to question its complexity when other libraries are able to do the same -- or more -- faster and with more interesting paradigms. React also doesn't handle side-effects well and instead creates escape hatches. Furthermore, corporate interests are driving feature development in a way that makes me feel like ulterior motives are getting involved.