react hooks: closer to a reactive library

react hooks: closer to a reactive library

With the introduction of react hooks, the library is closer to a reactive library, perhaps closer to the original intent of react. I use react hooks via my library scalajs-reaction.

One of the problems with react today is that it sometimes hard to reason about, hook functions (the new component hotness instead of classes) are not pure functions. State, ref, and other types of hooks mutate state underneath in the react “fiber” sub-system.

Hooks is closer to the more ideal approach. In the ideal approach, a little bit of the DOM is updated when the data dependencies that create that part of the DOM change. For example, if a text object changes, then a div or p should change in the DOM. You can do this with hooks, but it looks like (taken from the react hooks website):

function Example({suffix}) {
  const [count, setCount] = useState(0);
  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click Me - {suffix}
      </button>
    </div>
  );
}

There are others. Notably, useMemo, useCallback, useRef. These hooks take a list of dependencies and then memoize the return value when those dependencies change (using js Object.is). You can memoize the entire component using React.memo(Example) so that it only re-renders when the input properties change.

In other words, given a list of dependencies, whether state or an explicit list, recompute something. Recompute a or recompute the “render”.

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

Here, the memoized value will be recalculated if a and b change. You can use useMemo to compute a component as well. In fact, if you think about, you really, really want to avoid the cost of creating the component (which can be quite expensive even if its not touching the DOM) as much as you can.

A functional component (a hook component) is really efficient when it memoizes as much as it can in order to do as little processing as possible.

We can think of it like:

Inputs:

  • Component “props”
  • a
  • b
  • memoizedValue
  • “state”

when changed, leads to a creation of a component:

ifchangesinlist(props, a, b, memoizedValue) => "compute new component object"

If we can minimize the processing, make it more precisely mirror its dependencies, then we have reached the objective of creating an efficient component and ideally something that is more functional/pure. binding.scala does this–it’s called precise binding.

With react hooks, it’s kind of there. Here’s a piece of scala.js code that memoizes on the active address and computes a detail component and a summary component. Only when the address changes does the computation run. It assumes that the calculation of equality is less burdensome than the calculation of the actual objects. For many dependent objects, that’s probably true.

	// create a tuple of a detail and summary object when the "active" address changes
    val addressStuff = React.useMemo(active)(() => {
      (AddressDetail(active), AddressSummary(someArg, active))
    })

In the end, react hooks takes us one step closer to what binding.scala does to some degree. In fact, my react hook components have a substantial amount of memoization and dependency specification in order to reduce processing burden. In effect, it is creating a “reactive” component like binding.scala, just not as explicitly and rather heavily in terms of burden on the programmer. mobx is kind of like that but its focused on state management proper while the reactive concept needs to be a more expansive.

It would be alot easier if a real reactive framework was used that made the hook API easier. There are many examples, there have been a few created for scala.js or you can look at binding.scala as an example.

It’ll get there some day. Perhaps someone clever will use the “fiber” API to create it.

Comments

Popular posts from this blog

zio layers and framework integration

typescript and react types

dotty+scala.js+async: interesting options