Hacker News new | ask | show | jobs
by throwaway286 2381 days ago
Reasons I dislike hooks: The dependency array feels like a hack. It's way too easy to accidentally make an infinite loop. It's no longer safe to pass around functions; they have to be wrapped in useCallback(), which also has a dependency array. Yes, I'm aware of the eslint rules and the autofixer. It just feels that I'm so far away from writing javascript. Hooks are great when you're doing a simple setState or something, but with complex logic they get confusing quickly.
3 comments

Yes, I have had the same frustrations. The dependency array pattern feels very brittle. I have found two ways to deal with this:

1) Instead of taking a callback, can your hook return some value instead? I've found that in most cases, it can. That value can then be used to in some other (hopefully shorter) dependency array for a useEffect() hook that is a peer to your original hook.

2) In cases where your hook must take a callback, your hook should store the latest version of the callback in a local useRef. This add a lot more boilerplate code, but it feels safer to me than requiring the caller to manage the identity of the callback.

I've just had to make peace with the fact that I'm going to have to write a lot of boilerplate glue code around hooks. I try not to worry about it and I just hope the next version of hooks is better.

Or you can just use classes? Where writing these dependencies is actually formalized?
Your comment reminds me, I never found a perfect explanation what a negation inside the dependency array does. Does it simply negate the checked variable or just omits it from ever triggering eg useEffect? For example:

  const useClickOutside = (ref, onClickOutside, isActive) => {
    ...
    useEffect(() => {
      if (!isListening && isActive) {
        addHandlers(handler)
        setListening(true)
      } else if (isListening && !isActive) {
        removeHandlers(handler)
        setListening(false)
      }
      return () => {
        removeHandlers(handler)
      }
    }, [ref, !onClickOutside, isActive])
  }
I remember adding the negation here as the useEffect would fire needlessly (or wouldn't, can't remember), but wasn't entirely sure what it did. The whole hook is just for checking clicks outside an element.

Anyway, I agree with you that at times they can a bit too abstract for the fellow programmer. Without prior knowledge how hooks work it can be quite tedious to read (without proper documentation at least)

In this case, onClickOutside was probably a function that you defined inline in your parent component, which would actually be a new thing on each render.

(Something like <Blabla onClickOutsite={() => doSomething()} />)

Since a function is an object, and objects get compared by reference, a new function that does the same thing is not equal to the old function. Thus, this is a change.

Negating it turns that into a boolean, which gets compared by value. false is the same as false, so no change. Any type coercion (e.g turning it into a string) would've worked.

In this case, there's nothing specific to React or hooks, just plain old JS comparison.

Negation just includes the opposite boolean value in the array. _If_ that value at that index happens to be different than the last time the component rendered, the effect will run again.
The negation is effectively just a cast to boolean in that situation. If the value changes from truthy to not-truthy or vice versa, then the effect will run again, otherwise it won't.

Based on the name, it seem like onClickOutside will always be a function, so always truthy, so it's effectively the same as just leaving it out of the dependency array.

It feels like a hack, because it is a hack.

MobX does the same, automatically and under the hood. Vuex from Vue.js, likewise, but (currently) using Proxies. Svelte goes around the limitations of the language at compile-time.

Meanwhile hooks seem to be an attempt to write with performance in mind which in JS often isn't very effective and results in less readable code - unless you really know what you're doing.

I've noticed that even proponents of rewriting everything with hooks often don't fully understand how this thing works and because of that eventually write both less readable and less performant code.

My take is that this is the swan song of React. It solves a class of problems in React that are unheard of in other frameworks.

> It solves a class of problems in React that are unheard of in other frameworks.

It really seems that way. If your only way forward for your library is to throw everything out and introduce an entirely new (and hacky) paradigm, then you know you're on some incredibly shaky ground.

The old lifecycle methods surely had their issues, but I feel I'm done with React. I'm at my limit. I feel like it was barely a year ago we were re-learning the new new lifecycle methods that were supposed to fix everything. Hooks are just about the most janky ass shit I've ever seen from the JS community.

I can't believe I have to write this out. But functions do not have state. Functions. Do. Not. Have. State. It's not functional programming. It's not even functions with closures. It's some mutant bastard that needs to be killed in the crib already.