Remix.run Logo
CharlieDigital 5 days ago

There was a video recently talking about this with a good visualization [0]

The root cause of the problem is very fundamental to React and the nature of how the team designed the reactivity model. The TL;DR: React has an inverted model of state management [1].

In Vanilla, Vue, Svelte, Solid, any signals-based reactivity model, you explicitly opt-in to state changes by moving code into your reactive callback. In fact, in almost all of the code that you write, you typically explicitly opt-in to changes in state.

In React, you explicitly opt-out of state changes by moving code out of the reactive callback because the entire component function is the reactive callback (so any state in the path of that function that should not trigger a re-render has to be moved out via a hook).

This fundamental difference is what makes React hard to do well at scale because one needs to be very cognizant of where to place state inside a component and how to optimize for performance with memoization [2] (something Vue and Svelte developers pretty much never think about). The React team spent 2 years building the compiler to automatically manage memoization, but from the benchmarks I've seen, it's hardly made a difference.

In that time frame, Vue has been working on "Vapor Mode" [3] which bypasses the VDOM and brings it to parity with Svelte, Solid, and very close to Vanilla while still retaining a very easy to grok model of reactivity.

[0] https://youtu.be/INLq9RPAYUw

[1] https://chrlschn.dev/blog/2025/01/the-inverted-reactivity-mo...

[2] https://tkdodo.eu/blog/the-uphill-battle-of-memoization, https://tkdodo.eu/blog/the-useless-use-callback

[3] https://www.vuemastery.com/blog/the-future-of-vue-vapor-mode...

SebastianKra 5 days ago | parent | next [-]

Yeah, so you can't blame every performance issue you come across on a single topic, just because you've seen a YouTube video.

If you look at the performance graph, you'll see that most of the time is spent on recalculating css styles. Unfortunately, I can't drill into it because I'm not getting the same problems on my machine. Maybe it has something to do with the 1000s of rendered DOM nodes - no idea, but it has nothing to do with reactivity.

CharlieDigital 4 days ago | parent [-]

It's not because of the YT video; it's that the YT video provides a good walkthrough. I've been working with JS since the late 90's and have seen various front-end stacks come and go so to me, it is pretty obvious that React has this inverted nature and it is this inverted nature that is the root cause of why it's a very technical endeavor to get it to perform well at scale as it requires the engineer to think carefully about memoization and state placement -- something that doesn't happen in vanilla JS and early frameworks like KnockoutJS.

zahlman 5 days ago | parent | prev [-]

Interesting to contrast that with the assessment of React, qua critique of the Observer pattern in Brandon Rhodes' 2022 design patterns talk: https://www.youtube.com/watch?v=pGq7Cr2ekVM?t=28m45s . (Notably, later in the talk when he describes the Mediator pattern with a GUI-motivated example, he preempts by saying "I know I just told you to use React"... but then the description ends up sounding to me very much as if React is an implementation of the Mediator pattern!)

CharlieDigital 5 days ago | parent [-]

All modern FE frameworks are effectively observer patterns, it's that there are two camps on how those changes in the state are propagated.

In signals-based platforms (Vue, Svelte, Solid), the signal targets specific subscribers and callbacks where the developer has opted in to state changes.

In React, though `useEffect` looks a lot like Vue's `watch`, they behave entirely differently because the entire component is registered as the callback.

The code examples in my second link [1] above are worth a glance because it makes it very obvious how similar these libraries are when glancing at the code, but how different they are in terms of how they deliver update signals.