And then someone realized that browsers had long since reached standards parity, and javascript engines were getting really fast, and re-rendering the DOM was GPU-accelerated, and we just needed a simple little library. And Angular and React were born (and Ember but we don't speak of satan in this house).
React feels like it's following the exact same trajectory. It was created out of necessity, it was the best framework for a given point in time, but it's being stretched to handle the changing landscape, and it's feeling very kludgey. I expect that soon, very soon, someone will release the new paradigm. Probably just as soon as WASM is able to manipulate the DOM without JS hacks, which has been on the roadmap since before there was a road.
A useful feature that an insane majority of React devs have come to prefer is "downhill."
Kids these days.
Of course React devs will "prefer" it; they are the idiomatic way to adding state/triggering effects in React.
Devs use hooks primarily because hooks are better.
No, but the React ecosystem has almost collectively migrated away from class-based components or deprecated support for them.
> Devs use hooks primarily because hooks are better.
You avoid much of the dependency array issues by using class components. But you have to deal with `this` and it's significantly more keystrokes. It's more of a tradeoff in my opinion.
Also, devs don't use hooks primarily because they are better. They use them because they are idiomatic React in 2023.
:hmm:
yes, exactly!
> They use them because they are idiomatic React
right, and do you think it'd be easy for a feature to become idiomatic if it wasn't an improvement over the previous patterns?
Hooks were nice when they came out, but woefully under-documented. The fact that decent docs for hooks first development came out literally just this year when hooks have been out for years is kind of a shame.
As opposed to useEffect, most other hooks are fairly straightforward, but the fact it years of articles upon articles of mistakes and documentation to explain useEffect to both experienced and non-experienced devs alike shows that it really wasn't that great of an API for end users.
In fact the most common topic nowadays is how much you need to avoid useEffect, especially if you're not a library developer. None of this was documented/mentioned when it first came out.
Yes it does enable powerful workflows, but plenty of other frameworks/libraries handle the same thing in a nice way with much less drawbacks than react. (ex: automatic dependency tracking, etc)
The number of times I have to tell people to stop putting things in effects at all, or that the reason something isn't working is a stale callback due to missing dependencies in my opinion shows that its not really a good API for the average dev to be using.
The classes had closely related functionality spread out over several different methods making them hard to understand at a glance, function based components are very concise.
I think people who dislike hooks tend to overuse useEffect (it's rarely needed) and to do too much inside components rather than inside custom hooks.
I strongly suspect most HNers who are overly critical of React (and/or general web framework complexity) have never had to write and then maintain a large web application in jQuery.
I agree with your overall sentiment though.
React does not have routes or caching. It has about as much management as jQuery.
I don’t think application state belongs inside the rendering machine.
And I can see why the majority of HN readers would upvote the top level comment. Particularly since it's a popular re-hashed opinion.
Top comments aren't a good survey of the opinions of a specific community for that reason.
Full of (ex?) React devs. There are way better things out there now if you are starting a project from zero.
I agree that useEffect is bad.
But there are other SPA frameworks that do things better. In fact, just about any other framework does things better.
Vue3/composition API -- probably the simplest if you're coming from React. Definitely feels like it was inspired by hooks.
Svelte/store -- svelte has a term called 'hook' but it's unrelated. The equivalent would be Svelte's store.
The other frameworks use something called 'Signals' (Preact, Qwik, Solid, Elm and even Angular/backbone though I don't personally have in depth knowledge of all of these).
IMO, I would prefer Svelte if you are adventurous, or Vue if you like React but just want something like 'better React'.
This is easy to miss, so you install an eslint plugin to warn you about missing dependencies.
This causes the opposite problem - adding dependencies unnecessarily to silence the warning. Then you need to ensure those dependencies are stable to avoid running the hook on every render. Or ignore the warning with a comment, and you’re back to square one.
It’s all too easy to write an effect that either doesn’t run when it should, or runs when it shouldn’t. This is especially true for inexperienced developers.
I don't feel that React has reached that tipped point yet, but when it does I'm hoping we'll all move to Sveltekit or something like it.
SvelteKit is the first piece of web tech that I actually enjoy using. It’s the rare piece of tech that knows its lane, stays in it, and excels at it. It is marked by fewer features that work well together vs lots of features that sort of work well together.
You can read the docs in an hour or two and then start to get an understanding of what it’s doing.
Basically, it feels like it was made by someone who really understands the medium, thought deeply about how to distill that, and iterated to improve on it. (I get same vibes from Vue, but don’t have much experience there)
Edit: I'm just happy to find something that I like to make web stuff in. Now I can make some apps and actually enjoy the process.
So far it seems really amazing even for the most complex apps. I’ve built 5 nontrivial apps with 2 being 2+ year projects
I haven’t found a “ceiling” yet
Really? What caveats?
The only maybe oddity that I can think of is that if the deps array is omitted it always runs.
What API would you prefer? Something more like signals?
- Don't use effects for things that aren't genuinely effectful
- Put finnicky state management stuff in reusable utils instead of re-implementing it from primitives over and over again.
Neither is React-specific.
That's a bunch of examples where there are good alternatives.
I recalling using a lot of useRef to manually tune when to re-render, and in an ideal world I shouldn't be this granular about it.
You can certainly use one of the many React map wrapper libraries to lessen the pain, the pain is still very real, just dealt with by someone else.
Hooks imo are great, as they are pretty explicit and low level.
Nowadays signals are the new kid on the block, though mobx and similar have existed since forever in react land, but they have their own crazy edge cases like reactive loops and implicit behavior.
>But I don't think the "pretend it isn't" makes any sense
Well, let me expand a bit more. React's pitch is that view is a function of state, and essentially designed around the idea of an immutable state which re-renders everything when changed. It then offered some APIs to allow fine-tuning of rendering via hooks which either ties with data changes or some rendering life-cycle. To me the API screams of stubborn refusal to let go of the "view as a function of state" mantra: When you encounter a scenario where it breaks the mantra, lets add another lever to handle it. This lever also has to be pulled by you the developer, and it is up to you to know when to do it.
>I actually like React more than Svelte for many reasons, but magic compilers and templates are two big ones.
I don't really have an issue with compilers. They are the accepted magic that bridges between language for people and language for machines. The ideal language might be something that is functional and immutable language that it is easy for us to read, but compiles to the optimized, imperative updates that machine can run well.
I'd probably like React better if it had a compile layer on top of its more functional parts?
As for templates, JSX is probably the one great thing that came out of React. It is no wonder that many other frameworks are embracing JSX, but none of them are really forking the idea of hooks.
Lifecycles existed since the first version, and they were always absolutely critical. Hooks just fixed many of their issues and made them more elegant, composable, and simple to write.
If it quacks like a duck..
Hm, could you give an example of this ideal performant state management world? Angular? Vue? Svelte? MobX?
As another commenter mentioned, yes it's less low level than managing your own dependencies, but then again, most React apps are already wrapped in layers and layers of abstractions/context providers/selectors and use Jest as their test runner (which injects the global namespace with functions) and does other black magic and weird metaprogramming, so I think most React devs are already OK with less lower level stuff.
> What API would you prefer? Something more like signals?
Yes. Or full tree redraws like Mithril.js did 8 years ago.
useEffect is for side-effects.
Either way, this is different than what I mean with Mithril.js (it does full tree re-renders on event handler calls or when a manual redraw function is called).
I don't really agree with this except to the extent that, sure, it would be ideal for all APIs and indeed the entire syntax of your programming language to be so well-designed that you don't need any lint rules to catch common mistakes. I'd love it if my static type checker could catch most mistakes like this (although the boundary between linting and static type checking is fuzzy).
But in practice, you're almost certainly going to either 1) have a linter or 2) have strong resolve that you will not make any easy mistakes that a linter could catch. And if you're in the second boat, it doesn't make sense to single out this particular easy mistake, given that omitting arguments is always an easy mistake to make in JavaScript.
Which linting rule?
> That being said, I'm not sure that an API is well-designed if the only way to use it effectively is by forcing you to even know there is an eslint plugin to begin with, and then once you do know that, that means now you're being forced to add eslint to your project whether you like it or not. And sometimes eslint just stops working for who knows what reason, I have a developer on my team who's constantly dealing with issues where his eslint/prettier setup isn't running correctly.
Yes, not to mention eslint is slow with large codebases. Fingers crossed for the Rust(?) rewrite.
The only downside is that sometimes, the rule can end up being overly aggressive and tell you to add things which you explicitly don't want in your dependency array, but it's helpful probably 95+% of the time and not hard to opt out of or work around as needed.
As you already mentioned, it's not a silver bullet.
In my opinion if people have the rule installed and still feel the need to disable it then it shows there may be an issue with the API itself. This is coming as someone who has written React for a living since before even ES6 classes were supported.
I see it relatively often, including in production codebases where people just disable the rule because they don't know how to write the sometimes non-intuitive way to get around it.
useEffect breaks the sequential nature of your code and makes it feel like you're reading a script from a Quentin Tarantino movie.
Sequential, blocking modells like i.e. batch ETL jobs are just not the domain of the browser.
The DOM is almost entirely sync, all the operations on it are blocking operations (including things like replacing an entire document with innerHTML).
Thing about async/await.
const a = await doThing();
const b = await doOtherThing();
The order of the written code and the order of the executed codes are two different things. Being able to write code in proximate/logical order is a enormous relief, even if things don't actually happen like that.Same with hooks.
function SomeComponent({ someProp }) {
function doStuff() {
// do stuff
}
useEffect(doStuff, [someProp])
}
This trivial example is easy to read, but as more state and effects get added to a component, it quickly becomes fairly cumbersome to figure out the exact order in which your code runs.Google isn't even good at searching recent documentation, from my experience.