Even eliminating/substituting one of those (underlying language) can go a long way towards making FP UI a nicer experience. For example, Reagent[1] in ClojureScript has a state management approach that’s conceptually similar to hooks, but it uses the language’s own reference type semantics in a way that makes it much less awkward. It’s still a challenge to integrate (JS) libraries with side effects, but the community does a pretty good job of wrapping the more popular ones in idiomatic functional APIs.
The concept that ui = function(state) is incredibly powerful if you can stay within the concept. It can have some performance downsides, but even those can benefit from a language/foundation designed for it (eg with Clojure[Script]’s persistent data structures).
A complex UI component will usually contain different aspects A, B and C. Each of these requires hooking into the component lifecycle in various ways.
In a class/interface-based system, you have to sprinkle parts of A/B/C around in each of the lifecycle methods. The only way to abstract and contain this is to make `<A>` `<B>` `<C>` subcomponents, which comes at a significant cost.
Hooks instead allow you to group the bindings to the component lifecycle by aspect instead. You end up making a `useA(…)` `useB(…)` and `useC(…)`, which can not only run directly inline, but can also pass values directly from one to the other, setting up a complex, unconditional reactive data flow in a handful lines of code.
In my experience when hooks go wrong it's for a few reasons:
- people don't understand how they should useMemo for derived state, and instead emulate the old way with useEffect/setState
- react doesn't have an official hook for stateful derived props (i.e. useMemo which has access to the previous value), which leads to a hundred adhoc solutions in every code base
- people order their components the wrong way, nesting a source of truth inside a component that needs to derive from it
- sometimes, the side-effect free rendering model is a poor fit (e.g. mouse gestures, timers) because there is no guarantee every event is followed by a render... it's much easier to just use idempotent state changes on mutable refs here and tell the react core team to stuff it.
By the way, OO pretty much always implies retained mode UI. What React does it bring the benefits of immediate mode UI to a mostly retained world, and this is where FP excels, because you can use optics/lenses/cursors and all that meta-data-manipulation goodness to deal with mutations.
Derived state should be eliminated! If it can be derived, it’s not state.
If you aren’t trying to do derived state patterns, you don’t need to access the previous value. That’s a huge red flag. Likewise, “state” in useRef is a huge red flag. useMemo() is often a signpost pointing to bugs. If the useMemo cannot be removed without getting a different behavior in the application, that’s wrong—it might be slower, but the result should be the same with or without it.
It’s not a side effect free rendering model. Mouse interactions, requests, etc, are side effects; the pattern is side effect sets state and state defines the render. The problem happens when people try to “outsmart” this pattern and try to jump from side effect to outcome by subverting the state pattern, which makes them lose most of the advantages of react.
Any discussions around “triggering renders” or “preventing renders” before the component is behaving correctly are also big time red flags.
The problem is react keeps coming up with new leaky abstractions, instead of solid building blocks. Fashion is not a good way to do engineering.