The best take I've been offered is that the problem with Hooks is that they are kind of a quasi object-oriented solution (more or less it is encapsulating state with a setter/getter) shoe-horned into a quasi-functional render pipeline that demands functional purity at scale (lest your performance falls off a cliff from over-rendering or you end up with odd side effects).
That disconnect is why hooks just don't feel right.
Hooks are often side effects (but not always) and that sounds like "imperative" programming, but actually the side effect is what is inside the useEffect hook, not so much the hook itself.
The component is still a pure function. It "returns" side effects without executing them, just not as the function return value but through a side channel. But that side channel is sort of a clutch to achieve a pure relation from input to output, just that the output is now a return value from the function plus the sideeffects, which may be executed or not.
"Side effect" in my comment means an unintended side effect from an impure functional component (often a case of missing a useEffect, useMemo, or useCallback).
"Side Effect" in functional programming usually means the function is affecting something other than the output. And calling a hooks really doesn't, they just output the description of such an effect on a side channel.
> usually means the function is affecting something other than the output
That's exactly the point. If you leave an errant function call in a functional component, it's going to affect something other than the output of that component.
Isn’t the best of both worlds what you want? I do a lot of FP and OOP, and both worlds are not perfect. You want to mix them in a way you get the best out of both worlds.
Each has a different mental model and as Evan You said, the host language itself is not suited for a "pure" FP approach and you end up with a leaky abstraction.
As the article states, function closures are how classes are actually implemented in JavaScript so rather than mash the two together, it would seem a more cohesive mental model to simply use a class.
If you step back and look at the broader landscape of UI across desktop (Windows, Mac, Linux), devices (iOS, Android), and web: how many libraries and toolkits are FP and how many are OOP in their abstraction when it comes to managing components and object instances?
If you love OOP, then use Angular. It’s OOP all the way! But you will probably regret using it…
There are also a lot of UI frameworks that are completely functional. Take your pick. (ReScript would be the most obvious choice for a react developer)
But for a good reason the hybrid FP/OOP framework React on JS/TS is one of the most popular.
Classes are in fact "special functions", and just as you can define function expressions and function declarations, a class can be defined in two ways: a class expression or a class declaration.
The difference is that React functional components are effectively "parroting" FP and half-in (stateless components) and half-out (hooks). Solid, Preact, Vue are all-out.
F# has full support for programming with objects in .NET, including classes, interfaces, access modifiers, abstract classes, and so on. For more complicated functional code, such as functions that must be context-aware, objects can easily encapsulate contextual information in ways that functions cannot.
Classes represent the fundamental description of .NET object types; the class is the primary type concept that supports object-oriented programming in F#.
F# has full support for objects, which are useful when you need to blend data and functionality.
The underlying CIL representation of a lambda expression is an object; the functional side of .NET (whether C# or F#) is syntactic sugar on top of an OOP runtime.
As is often the case in CS, there are no definitions for plenty of concepts. FP is simply “programming with functions”. Most languages are multi-paradigm, and in my book any language that has lambdas is already functional - but it is a spectrum. Java has ways to express functional programs, but it is of course much less functional than, Haskell - which itself is also not 100% pure FP.
No, C# has plenty of features making functional programming possible, so it is definitely on the spectrum of FP languages.
Let’s go at it from the other direction - is Haskell functional? Because it has unsafe pointer access and unsafe IO, so it can pretty much express every C# program as well, right?
It literally says on the box that it's not a functional language and it's clear that the actual underlying CIL is class based. If you still want to call that FP, then by all means. But then so is C# since you can do nearly exactly the same things in C#.
That disconnect is why hooks just don't feel right.