Hacker News new | ask | show | jobs
by menssen 929 days ago
"Lots of people are still using it, but nobody can quite remember why."

I can remember why. This, and every other article I've ever read arguing to replace React with Web Components, completely misunderstands the point of React. It isn't about JSX. It isn't about encapsulation. It isn't about reusability.

It is about enabling a design pattern where *the user interface is a pure functional transformation of the application state.*

I kind of feel like people get tripped up by the fact that "virtual DOM" and "shadow DOM" sort of sound similar. They have literally nothing to do with each other. The React "virtual DOM" allows you to *completely re-output the entire user interface* on every state change, which is not possible with any other design pattern, and is not possible without a framework, because actually re-rendering the entire tree on every state change isn't performative (or usable).

Anybody who is advocating an alternative to React needs to do one of two things:

(A) Make a convincing argument that a different design pattern is better. Some things we've tried, which most people think are worse:

1. Using the DOM as your data model (jQuery)

2. Manually writing virtual representations of every view (Backbone)

3. Auto-magically two-way binding some data structure with the DOM (Angular 1)

4. Observables (Ember, maybe Angular 2+?)

(B) Advocate a framework other than React that uses the same pattern as React, but improves the usability. I think the two places there is the most room for improvements are:

1. Animations

2. useEffect() in general

I have not seen anybody successfully do either A or B.

Recommended reading: https://acko.net/blog/get-in-zoomer-we-re-saving-react/

14 comments

> 4. Observables (Ember, maybe Angular 2+?)

Don't forget Knockout which was the OG. You can also make the case that Svelte, Vue, and Qwik all are different takes on Observables (at least as much as Angular 2+ is) all with more or less magic and more or fewer escape hatches from Observable best practices to imperative(-looking) code.

I got a "What if we did Knockout but with with the compile-time benefits of TSX and Pure RxJS Observables?" itch a couple months back and have made some wild progress on it. I certainly don't think it is ready yet to advocate as an alternative to React, but it's probably in an interesting A4+B2 tangent on your map right here, and I find that interesting.

I remember 2012, ASP.NET MVC Razor pages, combined with individual page knockout Js. Actually worked quite well. Infact that system is still running for that company. Sure we could have made a angularJS 1.0 SPA at the time, but knockout meant we only had to apply the pattern to the pages that needed it.
I even used Durandal some in those cases where we really did want a SPA, but Knockout was good enough. That was something I appreciated about Knockout too was that the SPA framework was around it, not a part of it or implemented inside of it. Same with routing frameworks like Crossroads.js if you wanted something lighter than Durandal for just boring hash-navigation somewhere in the boundary spectrum between full SPA and MPA.

It was something that was also attractive about early React that React was similarly just a view engine and could do MPA or SPA or things in between and it didn't try to have the full kitchen sink. I don't know exactly where React stopped being that, but it always feels harder to argue that current React is "just" a view engine.

In the larger context here with this article, libraries that are just view engines should be great for building the internals of Web Components. (I'm hoping the view engine I've been working on might serve that role well, though with a dependency like RxJS I'm not sure if it would be towards the top of the list for many developers. It will certainly feel bigger than Lit, for example, no matter how well it tree shakes.)

I used Knockout to implement the very very very first ZeroTier network control dialog. Nice paradigm, but I think React beat it when things got really complex.
I got the same itch. Here's my thing. https://mutraction.dev/
Interesting, thanks. Your mutation tracker isn't enough like "pure" RxJS Observables for my tastes and what I've been doing with my itch, but you captured a few of the things I'm covering in my system and are probably the next closest I've seen to what I've been doing (and I tried to research a deep dive). I think the only other thing is that I took an approach that observable change bindings look different from static HTML-like attributes. I did that in part because that's how Knockout used to do it, and also in part because I think it should better facilitate SSG/SSR/progressive enhancement when I get back around to those ideas (it's not a current priority, but definitely an idea I'm tracking).
I was also influenced quite a bit by knockout. SSR was explicitly not a goal of mine. To me, SSR as a feature in a framework like this is mostly interesting in the context of server/client continuity like rehydration. But I have a feeling the constraints imposed by anything like this aren't going to be worth it. If I was doing pure server rendering, flat text templates seem like the sweet spot, since it's fundamentally not interactive. Render to html string and ship it. And if I was doing that, I probably would not choose to use javascript or observables/signals.
Knockout was designed in the heyday of the "progressive enhancement" era so it seems hard for me to claim a Knockout influence/inspiration if I'm not at least contemplating "progressive enhancement" of some sort. SSR isn't really my goal on that front but progressive enhancement/SSG (static site generation) certainly is a side goal for me (that's what I would like to have to self-host my own documentation site in something resembling the framework itself, rather than Jekyll or Docusaurus or whatever), but again not a main priority. It feels like the case for me where there is a lot of bleedover between SSR, SSG, and progressive enhancement and solving one basically solves all of them.

> I probably would not choose to use javascript or observables/signals.

This seems to be a case where we differ. In one obvious part because I don't tend to like signals and think they are very different from observables (because they are harder to encapsulate and offer fewer operators and tend to bleed too many accidental imperative escape hatches). Observables, to me, are just as useful to describe "open this file, read its contents, convert it from Markdown to HTML, then bind it to innerHTML here" on the server side as "fetch this URL, read its contents, convert it from Markdown to HTML, then bind it to innerHTML here" on the client side. Very different forms of interactivity, but there is still an interactivity there that a good observable pipeline can describe. Especially when you start to get into the idea that the difference between "open this file" and "fetch this URL" is tiny and can be dependency injected. Same component, slightly different inputs, slightly different expected outputs (you expect the server side binding to complete, whereas you expect the client side one to live for some time and continue to respond to different URLs from input).

I don't know much about the state of the art SSR/SSG, but in my mind, a whole document can be built synchronously, with a depth-first traversal of the templates/components. From that perspective the power and the constraints of reactive UI seem like overkill and limiting at the same time.

But anyway, the more UI frameworks, the more ideas get out there, the better. I'm hoping that some day in my lifetime, web UI gets "solved" and I'm not willing to believe that React is the answer. Does your project have a name yet? I'll keep an eye out.

> It is about enabling a design pattern where the user interface is a pure functional transformation of the application state.

This is only true for read-only components.

There is no "pure functional" when you introduce state and interactivity. See here: https://mckoder.medium.com/why-react-is-not-functional-b1ed1...

It is possible with React to write an application where components have no internal state, every component is "read only," and all UI changes are state transitions in (something like) a redux store.

This is rarely done, because there are pragmatic reasons (e.g., animations) not to, but it is possible.

The other mistake alternatives make is to try to make components having internal state "easier." It should not be easier! Every single useX() is a statement that "I am violating the proper design pattern of this application," and it's a feature not a bug of React that you have to be obvious and intentional about it.

If one of the most popular APIs is a violation, then maybe the pattern is broken.
I agree.

useEffect should have been named useDangerousSideEffectAndThisIsNotALifecycleHook and the js influencers should have never compared useEffect to component lifecycles and the React team should have updated their docs and not wait 4.5 years to do so and the dependency array should absolutely not be optional.

It’s a misremembering of history. The point of all the “pure functional” discussion was that rendering the UI now shouldn’t depend on how the UI was rendered earlier. The idea was to move away from the “create then update” paradigm to just “render”. React would be responsible for which elements need to be created and which can be updated in place.

To achieve that, it doesn’t matter whether the state is local to a component or global across the application.

> React would be responsible for which elements need to be created and which can be updated in place.

That works for simple, read-only components. The moment the component starts managing its own interactivity you end up needing state, and at that point things break down. If you need to change props, you have to essentially recreate the component by changing key [1], and at that point, what is the benefit of React?

[1] https://legacy.reactjs.org/blog/2018/06/07/you-probably-dont...

The idea of a reactive framework is that every interface component is either an event creator with no representation and that can update your application state, or an state viewer that has no inputs but can represent your state.

It's decoupling those two that brings all the power.

But the idea doesn't represent at all the web. So react is a mess of complex code trying to make the things you can create on the web behave like stateless pure components. Yet, it mostly works, and the react does indeed implement the idea that the interface is a pure functional transformation of the application state. (But I do disagree on claiming this the "entire idea", it's half of it at most.)

I too remember building interactive web sites with jQuery. It was never fun creating, for instance, an interactive list view where each item can be modified in client state and new items can be added. This was the problem for which React provided a major "Aha!" moment.
I would say SolidJS does everything better than React while keeping with the same general mindset. React spent way too much time worrying about DX to the point that now DX is really bad. Fine grained granularity is such a breath of fresh air to work with after the very large and very heavy brush that is the React “render the world every time anything changes” method (yes they try to be smart about it, but so far haven’t succeeded, the next compiler might solve it, but then we’re back to a black box of incomprehensible code soup to debug).
Thank you. Finally someone mentions Solid, it got all the good part of React, and none of the bloated parts.

Personally, JSX is the mainly thing that makes React attractive. As JSX is just modern E4X. It really ought to be put into the ES standard again instead of this Web Component stuff.

Another good JSX implementation (SSR) is: https://nanojsx.io

If you just want something light JSX, then this seems the way to go for now.

Preach. Angular 2+, btw, can (and should) be used the same way. V=f(S) is the only sane way to live.

Just don't change application state from a component lifecycle function and you're golden.

> (B) Advocate a framework other than React that uses the same pattern as React, but improves the usability. I think the two places there is the most room for improvements are:

> 1. Animations

> 2. useEffect() in general

I'm not normally one to "shill" a web framework, and I still mainly use (and love) React, but I'm curious if you've tried Svelte, because it definitely manages animations better than React, and has a different idea of state that's more ergonomic for many use cases (though I won't go so far as to say it's definitively "better" than useEffect)

I have to disagree.

It can't be that that design pattern because React did not invent it. Every server-side templating library is a pure functional transformation of application state. I can tell you that many people also ported that design pattern to JS.

What tripped library writers up is once they applied that pattern to JS, there is no way to actually define the transformation because to do that, you either write a template or the code-equivalent (i.e. using DOM as your data model). And that's where JSX came in.

Also, I think you have patterns completely confused.

Backbone is NOT in the same class as jQuery or React. Backbone is more like Redux, mobx, or React hooks because it's a data model library. In Backbone, you define "models" and "collections" -- basically, you use Backbone to store your application data in memory.

Backbone did have a views component to it but it actually could not generate any HTML. Look at this example from their docs:

  var Bookmark = Backbone.View.extend({
    template: _.template(...),
    render: function() {
      this.$el.html(this.template(this.model.attributes));
      return this;
    }
  });
It literally has jQuery and underscore.js! Backbone had no templating/HTML generation ability so when you used Backbone, you combined the fat Backbone classes with whatever horrific HTML-generation method you used to create a huge monster.

You can actually use Backbone.js with React since they are not in the same class. You would not, of course, because mobx is basically the same style of data modeling as Backbone.js but with almost no boilerplate.

The reason React uses a virtual DOM is because when React started, there were no (advanced) HTML templates yet. And it made it easy to setup listeners on elements, instead of manually adding it with `addEventListener()` and possibly remove them again with `removeEventListener()`. So the virtual DOM was really a game changer.

But Lit templates solve these problems in a more browser integrated way, without the need of a virtual DOM. How you manage the state is free to your choice, that is also not something exclusive to React and your favorite pattern can also be used with Lit. I wrote a tiny state management library (LitState [0]) which makes it very easy for multiple components to share the same state and stay in sync. I personally find it much more convenient and cleaner than any other state library I've used before. And it integrates very nicely with Lit.

[0]: https://github.com/gitaarik/lit-state

Solid.js comes to mind

- not a framework, but a library

- can do fine gained state updates without unnecessary re-renderings (reactivity)

- uses dom instead of vdom to produce best in class SPA performance

- uses jsx like react

Performance claim / proof: https://krausest.github.io/js-framework-benchmark/

With modern browsers, "completely re-output the entire user interface on every state change" is kinda viable. I recently wrote a trebuchet simulator app (hastingsgreer.github.io/jstreb) without a framework. instead I wrote a "rebuild UI" function that I call on every new state, and the user experience is super snappy
Weirdly I see that changing the "Projectile" selection to "P3" and then back to the original "P4" made the range drop way down, even though the new value was identical to the old value. But changing the "Main Axel" value made it jump way back up: https://imgur.com/a/boo5Xw3

So maybe some kind of "non-functional"/reused state issue exists regardless?

Oh, I don’t like the look of that. This may be a valuable lesson in “I should have just used a framework”
It doesn't seem to work at all in Chrome, so maybe not the best example.
Ah, global variables in modules have to be declared with var etc in chrome, but firefox and safari let it slide if you just assign. Fixed, but I guess I’m gonna have to set up a test suite
To add to that, one of the (largely justified) criticisms of React is that it, or at least the way it is commonly used, can have negative performance impact, and thus negatively impacts user experience. However, user experience is broader than just performance, and critically also includes that the application has as few bugs as possible and works well in the first place, and I feel that the model you describe greatly helps there, and it makes unit testing and strict static typing feasible to boot.

(Admittedly, these benefits haven't been thoroughly researched, as far as I'm aware, so it still mostly relies on gut feeling and experience.)

> Make a convincing argument that a different design pattern is better.

Simple Model-View-Controller pattern works well for JavaScript, just as it does for ASP.NET Core, JSP and JSF, Ruby on Rails, Django and so on.

Want proof? Browse this code:

https://github.com/wisercoder/eureka/tree/master/webapp/Clie...

This is the application: https://github.com/wisercoder/eureka

The server side style of MVC you describe is a far stretch from how MVC is practiced in retained-mode UI frameworks like UIKit, Cocoa, or Backbone. It’s possible to make this style work fine with careful design and planning; Apple built web versions of Pages and Keynote using SproutCore (which evolved into Ember?) in 2013-era.

In fact back then, everyone’s big app was MVC - usually Backbone. I worked on Airbnb’s host-side web app called “Manage Listing”, it had probably 30+ models, 150+ views, 100+ controllers in Backbone. There were many bugs in this scale of app around making sure the DOM reflected the latest change to some model. the engineering team regarded the more complicated screens as a nightmare to maintain in our fast paced environment with many teams touching the code.

Engineers at Airbnb started to adopt React as a solution to the problems we all experienced with MVC - not because it was a “hot new thing”. When React came out, it was widely regarded as weird - it was more like “eww, this smells of PHP and needs a weird compiler, but pure function of state is a lot better than fiddling DOM manually…”. Eventually we replaced Manage Listing backbone views with React components one by one until there was no backbone left.

React is still kinda weird but it does solve this problem the best out of the modern frameworks. It’s happy to over-render by default and prefers a correct DOM-for-state over everything else. It’s clear thought that the mental models popularized by React are worth it - now Apple and Google are switching to the React model in their own frameworks.

MVC is a proven and popular technology. It works very well, otherwise it wouldn't be so popular. I notice you allude to "problems we all experienced with MVC" without mentioning any. Surely if there are so many problems, you would be able to mention one?
I mentioned the main problem in the second paragraph:

> There were many bugs in this scale of app around making sure the DOM reflected the latest change to some model. The engineering team regarded the more complicated screens as a nightmare to maintain in our fast paced environment with many teams touching the code.

I've seen this same issue in Cocoa/UIKit/Android views. Older Cocoa in particular has a lot of hairy wiring up of signals and first responders and delegates. I think it's less of an issue there because the rate of change is typically lower; in web land we expect to deploy continuously and with a very high number of engineers, anything targetting the app store will usually deploy at most weekly (2 orders of magnitude fewer deploys) and with many less engineers (usually an order of magnitude at least. My hypothesis is the difference in change rate is why this kind of bug was more of an issue for web developers.

As parent pointed out, the MVC in server-side development only shares a name with the MVC in GUI development. Yes, MVC is a proven and popular name. But it means different things to different people.
Why would it be different? The concept of Models is the same whether it is client-side or server-side. These are objects that encapsulate business logic. The concept of Views is also the same. These are responsible for rendering the screen. The concept of controllers? That too is the same. Controllers determine application flow from one screen to the next.
The server is typically not a retained-mode kind of abstraction. Incremental view maintenance in retained-mode MVC systems is the main source of bugs. If you render the view from scratch on every request, it's more similar to React style immediate-mode UI than something like UIKit where you end up handling many UI events and keeping little bits of the UI up-to-date with model changes incrementally.
Well React is also a proven and popular technology, so there must be a reason why those using it chose it instead of using MVC.
Fair, though you can find plenty of stuff on the internet using React, but where the app/page itself performs abysmally. Meaning maybe there is some merit in approaches that are easier for lesser skilled teams to deliver in.
There is always space for options for "fast and easy" vs "robust and maintainable."

Nothing will ever beat the following as tech demo for how easy it is to make interactive UIs, but anybody who ever tried to reason through a large Angular 1 application would seriously hesitate to want to use it for something much more complicated.

<script src="angular.js" /> <input ng-model="inputValue"> <div>{{ inputValue }}</div>

useEffect is overused. People use it to compute derived values when that is not its intended use.

How many times have you seen the equivelent to a fullname = firstName + lastName inside a useEffect? Its outstanding. :(