It made it so much better to build apps vs. spaghetti jQuery.
I still have nightmares about jeeping track of jQuery callbacks
So the options are to 1. Code React all day and be happy with it. 2. Come up with reasons why it's bad.
There are many talented and intellectually curious people in the field which lean towards 2.
Nobody in their right mind is remembering to use `useDeferredValue` or `useEffectEvent` for their very niche uses.
These are a direct result of React's poor component lifecycle design. Compare to Vue's granular lifecycle hooks which give you all the control you need without workarounds, and they're named in a way that make sense. [1]
And don't get me started on React's sad excuse for global state management with Contexts. A performance nightmare full of entire tree rerenders on every state change, even if components aren't subscribing to that state. Want subscriptions? Gotta hand-roll everything or use a 3rd party state library which don't support initialization before your components render if your global state depends on other state/data in React-land.
> When they started adding new hooks just to work around their own broken component/rendering lifecycle, I knew React was doomed to become a bloated mess.
Hooks didn't fundamentally change anything. They are ways to escape the render loop, which class components already had.
> Nobody in their right mind is remembering to use `useDeferredValue` or `useEffectEvent` for their very niche uses.
Maybe because you don't necessarily need to. But for what it's worth, I'm on old versions of React when these weren't things, and I've built entire SPAs out without them at work. But reading their docs, they seem fine?
> And don't get me started on React's sad excuse for global state management with Contexts. A performance nightmare full of entire tree rerenders on every state change
I think it's good to give context on what a rerender is. It's not the same as repainting the DOM, or even in the same order of magnitude of CPU cycles. Your entire site could rerender from a text input, but you're unlikely to notice it even with 10x CPU slowdown in Devtools, unless you put something expensive in the render cycle for no reason. Indeed, I've seen people do a fetch request every time a text input changes. Meanwhile, if I do the same slowdown on Apple Music which is made in Svelte, it practically crashes.
But pretty much any other state management library will work the way you've described you want.
Even if you make a `createProvider` factory to initialize a `useMyContext` hook, it still requires what I mentioned above.
Compare this to Vue's Pinia library where you can simply create a global (setup) hook that allows you to bring in other hooks and dependencies, and return the final, global state. Then when you use it, it points to a global instance instead of creating unique instances for each hook used.
Example (React cannot do this, not without enormous boilerplate and TypeScript spaghetti -- good luck!):
export const useMyStore = defineStore("myStore", () => {
const numericState = ref(0);
const computedState = computed(() => reactiveState.value * 2);
const numberFromAnotherHook = useAnotherHook();
const { data, error, loading } = useFetch(...);
const derivedAsyncState = computed(() => {
return data.value + numericState.value + numberFromAnotherHook.value;
});
return {
numericState,
computedState,
numberFromAnotherHook,
derivedAsyncState,
}
});
This is remarkably easy, and the best part is: I don't have to wrap my components with another <Context.Provider> component. I can... just use the hook! I sorely wish React offered a better way to wire up global or shared state like this. React doesn't even have a plugin system that would allow someone to port Pinia over to React. It's baffling.Every other 3rd party state management library has to use React Context to initialize store data based on other React-based state/data. Without Context, you must wait for a full render cycle and assign the state using `useEffect`, causing your components to flash or delay rendering before the store's ready.
Your example would look like:
const id = useState(...);
const numericState = useState(0);
const q = useQuery({
queryFn: async (context) => {
const data = await fetch(..., {signal: context.signal});
const derivedState = numericState + data.something;
return `This is it ${derivedState}`;
},
queryKey: ["someState", id],
}, [id, numericState]);
...
if (q.isLoading) {
return <div>loading...</div>;
}
return <div>{q.data}</div>;
It'll handle cancellation if your state changes while the query is being evaluated, you can add deferred rendering, and so on. You can even hook it into Suspense and have "transparent" handling of in-progress queries.The downside is that mutations also need to be handled by these libraries, so it essentially becomes isomorphic to Solid's signals.
Here's how Zustand gets around this, and lo-and-behold: it requires React Context :( [1] (Look at how much boilerplate is required!)
React Query at least gives you an `initialData` option [2] to populate the cache before anything is done, and it works similarly to `useState`'s initializer. The key nuance with `const [state, setState] = useState(myInitialValue)` is the initial value is set on `state` before anything renders, so you don't need to wait while the component flashes `null` or a loading state. Whatever you need it to be is there immediately, helping UIs feel faster. It's a minor detail, but it makes a big difference when you're working with more complex dependencies.
1. https://zustand.docs.pmnd.rs/guides/initialize-state-with-pr...
2. https://tanstack.com/query/v5/docs/framework/react/guides/in...
---
I guess I could abuse React Query like this...
function useGlobalStore() {
const myHookState = useMyHook(); // not async
return useQuery({
initialData: {
optionA: true,
optionB: myHookState,
}
});
}
And you'd have to use `queryClient` to mutate the state locally since we aren't dealing with server data here.But here's what I really want from the React team...
// Hook uses global instance instead of creating a new one each time it's used. No React Context boilerplate or ceremony. No wrapping components with more messy JSX. I can set state using React's primitives instead of messing with a 3rd party store:
const useGlobalStore = createGlobalStore(() => {
const dataFromAnotherHook = useAnotherHook();
const [settings, setSettings] = useState({
optionA: true,
optionB: dataFromAnotherHook,
});
return {
settings,
setSettings,
}
});There is no real difference now between doing:
const [state, setState] = useState(42);
and: const [state, setState] = useState(undefined);
useEffect(() => setState(42));
They both will result in essentially the same amount of work. Same for calculations with useMemo(). It was a different situation before React 18, because rendering passes were essentially atomic.My major problem with React is the way it interacts with async processes, but that's because async processes are inherently tricky to model. Suspense helps, but I don't like it. I very much feel that the intermediate states should be explicit.
Their setup (component) functions are a staging ground for wiring up their primitives without you having to worry about how often each call is being made in your component function. Vue 3's composition pattern was inspired by React with hooks, with the exception that variables aren't computed on every render.
And I agree about Suspense, it's a confusing API because it's yet-another-way React forces you to nest your app / component structure even further, which creates indirection and makes it harder to tie things together logically so they're easier to reason about. The "oops, I forgot this was wrapped with X or Y" problem persists if a stray wrapper lives outside of the component you're working on.
I prefer using switch statements or internal component logic to assign the desired state to a variable, and then rendering it within the component's wrapper elements -- states like: loading, error, empty, and default -- all in the same component depending on my async status.
I understand that mixing declarative UI with the harsh imperative world is always problematic, but I think I prefer React's approach of "no spooky action at a distance".
As for speed, I didn't find any real difference between frameworks when they are used correctly. React can handle several thousand visible elements just fine, and if you have more, you probably should work on reducing that or providing optimized diffing.
For example, we're using React for 3D reactive scenes with tens of thousands of visible elements. We do that by hooking into low-level diffing (the design was inspired by ThreeJS), and it works acceptably well on React.Native that uses interpreted JS.
I find React perfectly acceptable, it's just global state management and a few flaws with its lifecycle that repeatedly haunt me from time to time. (see: https://news.ycombinator.com/item?id=46683809)
Vue's downside is not being able to store off template fragments in variables. Every template/component must be a separate component file (or a registered component somewhere in the tree), so the ease of passing around HTML/JSX conditionally with variables is impossible in Vue. You can use raw render functions, but who wants to write those?
JSX being a first-class citizen is where React really shines.
Problems are only truly solved by the folks who dedicate themselves to understanding the problem, that is: the folks working on web standards and the other folks implementing them.
It kills me to think of how amazing Web Components could be if those folks had started standardising them _now_ instead of in "competition" with userland component libraries of the time (while punting on many of the essential challenges of developing UI in the DOM those libraries were still evolving solutions for), and introduced more problems entirely of their own making.
I find it very intuitive, with the exception of useEffect.
```js
<div id="root"></div>
<script>
import React from 'https://esm.sh/react@19';
import ReactDOM from 'https://esm.sh/react-dom@19/client';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<h1>Hello, world!</h1>);
</script>
```
---
HTML's hello world:
```html
<h1>Hello, world!</h1>
```
---
JS's hello world:
Nothing, it was already done in HTML
import { useState } from "react";
function Counter() { const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
);
}---------- Svelte:
<script> let count = 0; </script>
<button on:click={() => count += 1}> Count: {count} </button> --------------- React: function Editor({ initialText }) { const [text, setText] = useState(initialText);
useEffect(() => {
setText(initialText);
}, [initialText]);
return (
<textarea
value={text}
onChange={e => setText(e.target.value)}
/>
);
}
---------------------
Svelte:<script> export let initialText; let text = initialText;
$: text = initialText;
</script><textarea bind:value={text} />
I really enjoy the syntax of Vue over React, and if it’s an application that’s complex enough to warrant something like React I’ll almost always reach for Vue so long as it also meets the requirements.
It's the ones that do some kind of server connection that introduce vulnerabilities and need active development.
But again, I don't think no support matters. How many DOM apis do you know of in the last 10 years that are essential? There are sites on JQuery 2 running fine.
Complex rendering model and hard to tame lifecycle since they ditched the class component. Very hard to get performant websites (but you're free to link me what you've produced with React and prove me wrong).
Also, biggest issue: severely misused for websites that are mostly static content and are nowhere near "app-like" nor have any particular reactivity need. 95%+ of react "applications" would've benefited from being written with a templating language instead.
E.g. Github was miles better under all aspects when it used ruby but of course somebody had to sell to upper management their promotion case.
[0]: https://github.blog/engineering/architecture-optimization/ho...
It requires a strong design system and it probably makes it harder to use some web APIs but those can be reasonable tradeoffs
EDIT: Found one that's going v5 soon, looks nice. https://www.nativewind.dev/