Hooks compose, whereas side effects and memoized values sprinkled through component constructors and lifecycle methods do not.
For example, the equivalent of useEffect required calls inside of componentWillMount, componentDidUpdate, and componentWillUnmount. You try and make something like this re-usable and you’ll be leaking details of your implementation across the whole component via inclusion in these lifecycle methods, not to mention any data you’re shoving onto the component instance. But it’s still doable.
Now, what if you wanted to use this re-usable behavior inside of another re-usable behavior? It gets complicated fast! Now your library needs to expose the lifecycle-updating methods of the underlying library, leaking details all the way down. Hooks are opaque from the perspective of lifecycle, while still having access to all the same… hooks.
I wish the current detractors would look back at this.
The options with life cycles are either huge life cycles with interspersed features OR super painful composition. I've been in big codebases with both and it was absolute hell.
Hooks aren't perfect or pretty but the fact that they can encapsulate AND compose makes them a million times better.
Those codebases are much easier to deal with now. Again, not perfect, but much better.
hooks were created to replace the functionality of mixins, not necessarily to replace lifecycles
The react team could have easily added a useDidUpdate() and useWillMount(), but they didn't because the new abstractions cover the mixins use-cases also work better than lifecycles as well (but somewhat less intuitive at first glance)
For example, the equivalent of useEffect required calls inside of componentWillMount, componentDidUpdate, and componentWillUnmount. You try and make something like this re-usable and you’ll be leaking details of your implementation across the whole component via inclusion in these lifecycle methods, not to mention any data you’re shoving onto the component instance. But it’s still doable.
Now, what if you wanted to use this re-usable behavior inside of another re-usable behavior? It gets complicated fast! Now your library needs to expose the lifecycle-updating methods of the underlying library, leaking details all the way down. Hooks are opaque from the perspective of lifecycle, while still having access to all the same… hooks.