Hacker News new | ask | show | jobs
by jitl 1551 days ago
This really hit home for me:

> I do not exaggerate when I claim that I find a dozen of hooks-related problems every single week while reviewing code.

I also see sooo many issues when reviewing code like leaked event listeners, unstable props causing re-renders, etc. And these issues show up from teammates who otherwise write impeccable and trustworthy PRs in other regards.

I enjoy writing hooks style code, and for me reasoning about lexical scope & closures is second nature. But for many engineers used to OOP, hooks code is the first time they’re asked to do this kind of reasoning since leaving university. In OO Java/JavaScript, it’s very normal to declare a new class and have the only two scopes be the current function’s locals, and class instance members. Hooks code on the other hand can easily reach two or three layers of local closure scopes, each with different lifetimes. I think this is fun and clever, but I also prefer to maintain boring and simple code… I’m worried that hooks ramps up trivial complexity too much in exchange for often-unneeded power.

On the other hand, function components and hooks tend to guide people more toward splitting up their big mega components into smaller parts. At all companies I’ve worked for, product eng across the stack tends to produce mega objects cause it’s easy to just keep adding private methods to do one more thing, and splitting up responsibility of state encapsulation takes some extra reasoning. At least with FC/hooks, the API and linter force you to unroll funky logic or loops into sub-components, since you can’t call hook in loop or conditional.

6 comments

> I think this is fun and clever, but I also prefer to maintain boring and simple code… I’m worried that hooks ramps up trivial complexity too much in exchange for often-unneeded power.

I've been in and out of the React world for the last 5 years, and this statement hit hard for me. Between the major shifts in best practices, the abstractions on top abstractions and constantly tripping up on the slight syntax differences between JSX and markup, so many commercial React apps I've worked on make me want to pull my hair out, and not just because of hooks.

In hindsight, the virtual DOM hype has not lived up to expectations, and I find newer frameworks like Svelte to be so much easier to work with. With the amount of React code running today, it's hard to see a future without it, but I'm so ready for it to be supplanted by something simpler.

I have to say, I've never felt this in the Vue ecosystem. It's relative simplicity in state management, and slower relative paradigm shifts have been pleasant. The 2->3 shift wasn't perfectly done (and it did split the state management stories), but overall I've felt like I "kept up" fairly easily, compared to React.

And that's while writing a lot more React than I did Vue.

There’s too much framework in Svelte for me; I don’t like feeling so estranged from “regular” code. I think SolidJS is more appealing - although it might be even more clever than React…
To each their own, I guess.

I haven't built anything with SolidJS, but it's not my style based on the docs. It is interesting that you bemoan being so far from "regular" code in Svelte when it's basically a minimal superset of HTML, as opposed to Solid, which uses JSX (which feels very far from "regular code" to me). Also given the fact that Svelte is a compiler that emits vanilla JS, I have a lot of more control over performance, compile-time checks for things like a11y, unused deps, etc. I'd argue that svelte source code looks a whole lot more like it's output than SolidJS does, but that't just me.

It's easily the best overall front end developer experience I've seen, but I've only built smaller projects with it so far.

Interesting - I began using Svelte after years of using React, and I found it to be far less framework-y. I feel much more connected to what's actually happening, but I also haven't built out a very complex application with it yet. Is it the weird $: reactive model that you don't like? That was the weirdest part for me, even if it's supposed to be "normal" javascript.
Svelte feels very minimal to me. It is regular JS where I can say "shove the value of this variable onto the web page and rerender that component when the value changes" but saying all of that is just $:
All frameworks are good when they are new and different from the one you're using, and bad when you've worked with them for a few years
I had bad feelings about React when I first saw it. Then I saw RiotJS and that just clicked for me. I like Svelte too - haven't put it in production tho
> I also see sooo many issues when reviewing code like leaked event listeners, unstable props causing re-renders, etc. And these issues show up from teammates who otherwise write impeccable and trustworthy PRs in other regards.

I think this is true. However, I don't think I saw less bugs with `component(Did|Will)(Mount|Update|ReceiveProps)`. Lifecycle events are intrinsically about state management, and that has always been the root cause of many bugs. This isn't a React specific problem either. Back in AngularJS 1.2.x days, the $scope and digest cycle was the source of many bugs. In Backbone, people's two-way binding between views and models were the source of many bugs.

Are React hooks complex? Yes. I don't think they're worse than what existed before though.

I don't entirely agree - the old React way was verbose and explicit about when events are executed. This allowed developers to more concretely reason about logical workflows in applications.

With hooks, we traded verbosity for a single interface that does it all (assuming you know how to hook up your dependencies correctly, or compose helper hooks to manage state comparisons). Hooks allow you to do mostly anything lifecycle methods did, but they're a lot trickier to reason with, review, and develop.

This all goes away if all your developers are functional maestros - in practice, it's lead to buggier code across our various frontends.

My experience has been pretty different. With lifecycle methods, you had the implementation details of a single concern split across the constructor, multiple lifecycle handlers, and sometimes even the render function when refs were involved. Hooks can express full concerns in a reusable way. This is a valuable abstraction that previously required complex higher order components to do.

People would also constantly get tripped up over `this.props` and `this.state` when it came to computed state values. Now a simple `useMemo` simplifies and expresses that intent way better than setting something conditionally in the constructor and doing a bunch of conditional checks on componentWillUpdate before calling this.setState again.

Edit:

Oh, and the improvements with Redux are life-changing. The `useTypedSelector` UX is so much better than writing a mapDispatchToProps, mapStateToProps, and then having a bunch of merging ceremony there.

I'd chalk it up to a difference of opinion then - I like simplicity, but I'd take verbose clarity over it any day. Having to explain to newer engineers the nuances of hooks (and the permutations required to wrangle them in) is harder for me than saying "this exact lifecycle method is what you're looking for, take a look at its documentation".
Chalk it up, then. I've never had a problem explaining the nuances of hooks, and there isn't actually that many permutations needed to wrangle them in indeed - but our engineers are usually quite quick to pick these things up and understand the interface without having to worry about the implementation anyway.
It is a challenge for many developers. It’s taking developers I work with between 2-5 months to really grok it.

So if you’re working on multiple tiny changing projects, or with contractors who are only gonna be around for 6-12 months, it’s possibly not worth it.

But if you’re working on a project that needs to stick around, and the people you’re working with are colleagues who will remain in the company even if not on the same project, the training is totally worth it, IMO.

Thank you, glad to hear that our React-Redux hooks are useful!

And yes, the fact that the hooks work so much better with TS than `connect` was one of the major reasons why we now recommend the hooks API as the default.

> I do not exaggerate when I claim that I find a dozen of hooks-related problems every single week while reviewing code.

I’m curious if the author sees dozens of problems every week in code review due to, say, unexpected null/undefined values, mistyping variable or attribute names, using the wrong number of equal signs, failing to catch errors and handle rejected promises, etc.

I'll give you my answers to your questions as someone writing production JS/TS. Answering your questions is precisely illustrative of why hooks issues are hard to catch.

unexpected null/undefined values

This is an issue and has led to bugs. Switching to TS and making the compiler flag them fixed it.

mistyping variable or attribute names

Not an issue. Code obviously breaks if you have incorrect names.

using the wrong number of equal signs

This is an issue but causes few bugs. Linting generally catches it, though cute boolean punning still bites us.

failing to catch errors and handle rejected promises, etc.

This is an issue and has led to bugs.

Pretty much anything where there's some implicit details that the compiler or linters can't reason about programmers find a way to get wrong. One thing I like about the hooks linter setup is that what it encourages you to do by default will prevent most bugs, only lead to potential performance issues, unnecessary rerenders, unnecessary refetches.

>> failing to catch errors and handle rejected promises, etc.

> This is an issue and has led to bugs.

> Pretty much anything where there's some implicit details that the compiler or linters can't reason about programmers find a way to get wrong. One thing I like about the hooks linter setup is that what it encourages you to do by default will prevent most bugs, only lead to potential performance issues, unnecessary rerenders, unnecessary refetches.

This is something that can be mitigated via the no-floating-promises linter rule if you're using TypeScript[0]. For the cases where you actually want to just swallow errors, you can just add a `.catch(noop)`. This makes such situations explicit. You can get even stricter with the no-misued-promises rule[1].

[0]: https://github.com/typescript-eslint/typescript-eslint/blob/...

[1]: https://github.com/typescript-eslint/typescript-eslint/blob/...

Thanks for those references, I've recently been handed an overwhelmingly "JavaScript" project and this'll be a great way to expose some overlooked code paths.
> Pretty much anything where there's some implicit details that the compiler or linters can't reason about programmers find a way to get wrong.

touching on an immortal truth!

Can anyone here recommend a blog post that go through the most common mistakes when using hooks?
Don't have a blog post, but the majority of issues you will find are with dependencies arrays for useMemo/useCallback/useEffect.

Most common I see:

1: Missing dependencies (for folks who don't use the linting rule)

2: Not understanding reacts async state model: example being expecting state to have been updated immediately after calling setState inside an effect

3: Not understanding closures: an example is creating an interval in an effect that uses and updates a useState value, but being confused why it isn't updated when the interval repeatedly fires.

I agree wholeheartedly with the author. Hooks are powerful and even I was super excited to start using them when they released. But now I think the hooks paradigm leads to even worse and bug-ridden code than what we had before.

The docs are pretty good, but don't seem to cover very well that when using hooks you really need to know JS well, which includes equality in JS, closures, etc otherwise you will be guaranteed to shoot yourself in the foot.

There is so much more business logic mixed in with component code at seemingly random in projects as well such that it makes finding where "stuff" happens even more difficult than before.

The article here ("Hooks Considered Harmful") actually probably would be better called "Most Common Mistakes When Using Hooks". It seems to cover the most common ones I'm aware of.
After dealing with years of the hoc stacking hell, hooks makes the logic sharing problem so much easier. It's like switched from callbacks to async/await. I am not sure if there are better solutions.
"you are using hooks wrong"

"there is room to improve documentation blabla"