Additive Ideas vs Paradigm Shifts

One reason I like Tailwind is that it doesn't make it any harder to do things the old way, with plain old CSS. In fact, since it auto-generates theme custom properties, in some ways it makes things easier, even if you don't use the utility CSS classes.

Tailwind layers on top of the existing paradigm in a purely additive way.

React is the opposite. For starters, intends to replace a broad swath of paradigms — HTML and content, JavaScript and interactivity. And since it expects everything you do to work within its paradigm, data fetching, authentication, routing, and everything in between has to be built specifically for React.

Yes, it is possible to slot static HTML in with dangerouslySetInnerHTML, and it is possible to use non-React JavaScript libraries. But in the latter case, you have to implement it as a black box, wiring up each and every API interface to have any kind of control from the React paradigm. It's an escape hatch that leads to a bottomless pit.

React brought three powerful tools to UI development:

Declarative component UIs with JSX

Synchronizing components with host element updates using a virtual DOM reconciler

Scheduling renders with a reactive data model

Incidentally, the JSX and virtual DOMs are intrinsically tied together if you want any kind of dynamic updates, but I separate them because it's possible to generate static UI with just JSX.

And you know, I think JSX is the worst templating language out there, aside from all the other ones. It looks and behaves similar to HTML. Interspersing JavaScript in the template means you don't have to learn an entirely new syntax for your templating language. You just follow some simple rules, and that's that.

Virtual DOM is a pretty good way to reconcile component updates to DOM elements. Svelte has argued that it's not as good as surgical updates generated by a compiler (and that's probably true), but in most practical applications, it's good enough. (And curiously, most of the arguments against React in that blog post are related to React's reactivity model, not its virtual DOM.

Reactivity really is the bugbear of React. The promise is simple: Follow its not-so-simple rules, and any time you follow the ceremony of updating state, everything in your UI will magically resolve exactly as it should.

Unless you need to do side effects.

Or you are doing something async.

Or you don't memoize things properly. (But good news, the compiler fixes all of that for you, right?)

Make sure you put your Suspense boundaries in the right place. useDeferredValue and useTransition can help when doing async stuff, just make sure you're using the right one in the right place. Use effects to synchronize with external systems, just be sure to put the right dependencies in, and remember that the effect might run twice because of reasons, and on second thought maybe don't use effects. Like, ever.

It's impossible to not recognize that React and the ideas it's generated has pushed the state of the art forward in big ways. We wouldn't be where we are without the innovations that came from the React team and community.

I've been using React for over 10 years. I've gotten really good at it. Most of that time, I've been happy to jump through its hoops. I didn't realize it was at the expense of getting really good at doing things not in React.

Lately I've started to feel like things could be better. Do we really need to ship all of the JS for the entire app, like every React meta-framework has you do? Or can we get by with HTML and just a bit of JavaScript sprinkled in?

Astro seems to think so. The team building Remix v3 seems to think so. I want to think so.

I want tools and frameworks and packages that layer on top of what the platform already gives me, that don't require me to work within their funky paradigm, but allow me to use what I need, and then get out of my way.

I think Remix v3 does this really well.

The UI package takes the good parts from React — JSX and Virtual DOM — gets rid of the reactivity and replaces it with a manually reactive system focused on events. An event happens, you mutate variables, and you call handle.update(). Boom — your component re-renders. It's lightweight, and it's fast. (I've managed to run handle.update() in requestAnimationFrame loops at native speed; React's reactivity model suffers a 4x speed penalty.)

Frames and the hydration model encourage you to build your app with just static HTML first, and then sprinkle in the interactivity later.

You start with the base paradigm — HTML, CSS, and JavaScript — and you add on new ideas without replacing or invalidating what's already there. I like that.