Hooks feel like a good system that stopped right before it became pretty. Like, an componentDidMount event is a now useEffect with no second argument? But a componentWillUnmount event is now a useEffect, with no second argument, that returns a callback?
It's powerful, and it's not that hard to use, but it's cryptic and random.
Why call it useEffect instead of some other more meaningful phrase? I mean, "componentDidMount" tells you exactly what it is. Does "useEffect"?
Why should onload things be a function, but onunload should be a function returned by a function?
Why does useRef give you a thing used for attaching handles to DOM elements so you can refer to them elsewhere, AND it gives you an object whose .current property can be used as a variable that persists without causing a render?
It's like someone complained that there were two many functions in the API, and their names were too long, so they overcorrected in the opposite direction.
You're translating the class-based way of working in React against hooks, without stopping to consider understanding hooks as a first-class principle instead of a translation.
It's called useEffect because it runs on the (side)effects observed by the dependency array. An empty array happens to happen on mount. useEffect returns a teardown function which happens to correlate with unmount when an empty array is passed.
It's called useRef because it returns a (mutable) reference that's detached from the reactive layer. The DOM element connection is just sugar.
I agree that useEffect has a lot of footguns, but this position seems very shallow. The whole pattern changed, it doesn't make a lot of sense to continue comparing the two
Yes, you're correct, and he got it wrong when talking about it.
This kind of goes towards my point. useEffect works, and it works great. It's flexible and powerful - but it's cryptic as hell, and the interface is just "memorize it because you have to", not something more intuitive.
What if useEffect required a 2nd argument, but you had the option to pass in self, which would still offer the onMount behavior, by mirroring the way the other form of it worked? Bada-bing, now there's no exception to the rule. What if the 2nd argument was a named argument, like "trigger"? Now it's self-documenting. What if the teardown function as an optional 3rd argument with a name, too? Now it's easy to glance at a useEffect and tell whether it's declaring a teardown without carefully reading the body. etc etc.
He says "useRef offering a connection to DOM elements is just sugar", but that's not sweet, to me. There's no reason one thing should do two totally different things. That's exactly what I'm complaining about.
In the beginning React was a great example of high payoff of just a little bit of "memorize it because you have to."
For many projects it felt like it gave you super powers, for some it was just OK, and a small percentage of interfaces just didn't fit the React model and were better some other way.
It appears to me that since then that small percentage has gotten the development attention (understandably), creating a React which is more well rounded and broadly useful but there is more to learn and the super power feeling has been dulled a bit.
Overall it has improved but I still wonder what a React that was more specialized for those interfaces where it really works great would be like.
I definitely agree useEffect is awfully cryptic and easy to get wrong, and I'm happy to be corrected... but I'm not sure how I'm wrong about it running on every render when there's no 2nd argument?
I kinda wish they'd made it easier to do the common operations like onMount and onUnmount by providing some simplified wrappers around useEffect (with less power comes less responsibility... or something). Of course we can make custom hooks for those, but having the well-trodden paths be paved is always nice.
I misunderstood what you were saying about it running on unmount when the dependency array is empty.
Of course, it does run on unmount when the dependency array is empty, and it runs on unmount when there are dependencies in the dependency array.
But upon re-reading what you said, I think your intent was that it's comparable to componentWillUnmount() in class-based React only when the dependency array is empty (because otherwise the cleanup function also gets called when dependencies change).
My apologies, as I never really used class-based React, so the distinction was lost on me (and also forgot about the cleanup function being called between useEffect callback calls)
Oh gotcha, no worries. I'm not sure if you meant to reply to me anyway, my comment was in regards to knodi123's original comment that linked to an article of mine and said it was wrong about when useEffect re-renders – but the comment was edited and that part is gone, so now mine looks entirely crazy haha.
I edited it because I realized I was wrong - I was confused by the difference between no 2nd argument, and [] for the 2nd argument. But again - as a full time professional web developer, who has being using React for a few years, I think that also goes towards my point. ;-)
useRef does only one thing. It returns an object where the latest value is current. That’s all. Element components will use a ref to put a reference to themselves if you pass one via the ref prop. They can be used to store anything else too. Nothing magic or confusing.
If anything it’d be weirder to make this two separate concepts. What would they even be? What does a ref not already do, as a box that contains an arbitrary value, that we’d want from a box with an element in it?
This is correct, but I think the point nickdandakis was trying to make is that useEffect does not directly correlate with componentDidUnmount because useEffect's returned callback could be called in the middle of the component's lifecycle, not only when the component unmounts.
Err.. no, you pass a callback (let's call it the effect callback) that returns a callback (the cleanup callback) to useEffect. The effect callback gets called when the component mounts, and when dependencies (if any) change. The cleanup callback gets called when the dependency unmounts (and will not be called at other times in the lifecycle)
edit: I'm wrong, the cleanup is also called when dependencies change, just not on mount.. eg. it gets called before the effect callback being called again.
In other words, the cleanup function is called on unmount when useEffect is called with an empty array, and if useEffect has dependencies, it will also be called when those dependencies change
It does, but it runs on more than just unmount when the dependency array is not empty. It runs anytime any dependency in the array changes. This is an important distinction. For example, event listeners added in useEffect will be removed anytime a dependency changes. Then a re-render will occur and they will be added back in the next useEffect body execution. This differs from componentDidUnmount which obviously only runs on unmount and never in the render cycle
I love hooks, but I think they made a few mistakes. The weirdness around useEffect having different behavior with no second argument vs [] is one of them.
And they should have included a useUnloadEffect() by default, even though it's trivial to write, just for clarity. It's way too easy to miscount the number of ()s in useEffect(() => () => {}, []);
They also should have included a few other basic hooks, like useStableValue() for just computing a constant once on first render.
I hate the name `componentDidMount` though. useEffect seems much better to me.
It seems like almost always if you want to do anything during unmount, it's cleaning up something you set up in a useEffect call, which you also want to do any time the useEffect call's dependency list changes, so it being part of the useEffect hook helps programmers fall into the pit of success. React's older class-based components had the unmount handling separate (componentWillUnmount), and in my experience on a large React codebase, many if not most components using componentWillUnmount were subtly flawed and failed to handle certain cases where props or state updated in a way that should have caused effects to be cleaned up or adjusted. React's useEffect hook way of grouping up effects with their own cleanup code helps people handle these cases correctly even without realizing it sometimes.
This is incorrect. useRef takes an initialValue argument, but it does not treat functions as lazy initializers like useState does. If you pass a function to useRef, you’re just going to get that function as the initial value.
If you want to lazily initialize a ref, you need to manually check if it has been initialized and run your expensive code if it hasn’t. Dan Abramov provides what appears to be a pattern officially recommended by the React team: https://github.com/facebook/react/issues/14490#issuecomment-...
It's not a function, it's an immediately-invoked function expression.
Edit: I guess it would be a bit inefficient to invoke the IIFE every render just to initialize the value once. Probably better to use useMemo with [] as the dependency list then, either one achieves the same result.
I missed that you were immediately invoking the function expression. But yes, that doesn't save you from running the expensive calculation every subsequent render.
> Like, an componentDidMount event is a now useEffect with no second argument?
Doesn't useEffect with no second argument run after every render? componentDidMount's equivalent is for an empty dependency array in the second argument?
Bingo. And this is the situation we are in. Even knowing basically what's going on and still getting it wrong with just the bare minimum choices of ,_ or ,[].
Mix in actual deps coming from other hooks, changing as the components re-render, and multiple layers of "custom hooks" with more of the same and it's like trying to hold back a tsunami.
They could've easily added useOnMount, useOnRender, etc
It doesn't matter that they are sugar. Just like useState is built on useReducer, it would be massively helpful to simplify and clarify what's going on.
They very purposefully didn't add useOnMount because of the subtle bugs developers introduce when they do things only on mount. People would react to the initial props in componentDidMount, but never respond to their change.
Hooks, and useEffect, was supposed to be designed in a way to eliminate that class of bugs.
I totally agree about the cryptic-ness when it comes to useEffect and its usage, but the neat thing is that you can trivially write your own thin wrappers with better names! I think `useEffect` is the best name possible given how broad its use cases are, because it's literally for any function side effect.
It's powerful, and it's not that hard to use, but it's cryptic and random.
Why call it useEffect instead of some other more meaningful phrase? I mean, "componentDidMount" tells you exactly what it is. Does "useEffect"?
Why should onload things be a function, but onunload should be a function returned by a function?
Why does useRef give you a thing used for attaching handles to DOM elements so you can refer to them elsewhere, AND it gives you an object whose .current property can be used as a variable that persists without causing a render?
It's like someone complained that there were two many functions in the API, and their names were too long, so they overcorrected in the opposite direction.