IMO, a big part of it is the lack of competition (in approach) exacerbated by the inability to provide alternatives due to technical/syntactical limitations of JavaScript itself.
Vue, Svelte, Angular, Ripple - anything other than React-y JSX based frameworks require custom compilers, custom file-types and custom LSPs/extensions to work with.
React/JSX frameworks have preferential treatment with pre-processors essentially baking in a crude compile time macro for JSX transformations.
Rust solved this by having a macro system that facilitated language expansion without external pre-processors - e.g. Yew and Leptos implement Vue-like and React-like patterns, including support for JSX and HTML templating natively inside standard .rs files, with standard testing tools and standard LSP support;
https://github.com/leptos-rs/leptos/blob/main/examples/count...
https://github.com/yewstack/yew/blob/master/examples/counter...
So either the ECMAScript folks figure out a way to have standardized runtime & compilable userland language extensions (e.g. macros) or WASM paves the way for languages better suited to the task to take over.
Neither of these cases are likely, however, so the web world is likely destined to remain unergonomic, overly complex and slow - at least for the next 5 - 10 years.
Frameworks that go that route typically activate this toolchain by defining a dedicated file extension (.vue, .svelte).
This custom toolchain (LSP, IDE plugins) presents a lot of overhead to project maintainers and makes it difficult to actually create a viable alternative to the JSX based ecosystem.
For instance both Vue and Svelte took years to support TypeScript, and their integrations were brittle and often incompatible with test tooling.
Angular used decorators in a very similar way to what I am describing here. It's a source code annotation in "valid" ecmascript that is compiled away by their custom compiler. Though decorators are now abandoned and Angular still requires a lot of custom tooling to work (e.g, try to build an Angular project with a custom rspack configuration).
JSX/TSX has preferential treatment in this regard as it's a macro that's built into tsc - no other framework has this advantage.
In my opinion, the core functionality of React (view rendering) is actually good and is why it cannot be unseated.
I remember looking for a DOM library:
- dojo: not for me
- prototype.js: not for me
- MooTools: not for me
- jQuery: something I liked finally
Well, guess what library won. After I adopted jQuery, I completely stopped looking for other DOM libraries.
But I still needed a template rendering library:
- Mustache.js: not for me
- Handlebars.js: not for me
- Embedded JavaScript Templates: not for me
- XML with XSLT: not for me
- AngularJS: really disliked it SOO much*
- Knockout.js: not for me
- Backbone.js with template engine: not for me and actually it was getting popular and I really wished it would just go away at the time**
- React: something I actually liked
You must remember that when React came out, you needed a JSX transpiler too, at a time when few people even used transpilers. This was a far bigger obstacle than these days IMO.
Which leads to my hot take: core React is just really good. I really like writing core React/JSX code and I think most people do too. If someone wrote a better React, I don’t think the problem you mentioned would hamper adoption.
The problems come when you leave React’s core competency. Its state management has never been great. Although not a React project itself, I hated Redux (from just reading its docs). I think RSC at the current moment is a disaster — so many pain points.
I think that’s where we are going to see the next innovation. I don’t think anyone is going to unseat React or JSX itself for rendering templates. No one unseated jQuery for DOM manipulation — rather we just moved entirely away from DOM manipulation.
*I spent 30 minutes learning AngularJS and then decided “I’m never going to want to see this library again.” Lo and behold they abandoned their entire approach and rewrote Angular for v2 so I guess I was right.
**It went away and thankfully I avoided having to ever learn Backbone.js.