Saturday 12 December 2020

Can the useCallback React hook be used conditionally even if it breaks the rules of hooks?

I am trying to figure out a way to be able to memoize React components by specifying particular props.

For instance, if you use React.memo — it memoizes the component based on all props.

What I am trying to achieve is being able to pass particular props as a dependency to a util (say, SuperMemo) and the component will be memoized based on those props. The approach is very similar to what recompose — compose the component before export.

Here's an example code

import React from "react";

const isFunction = value =>
  value &&
  (Object.prototype.toString.call(value) === "[object Function]" ||
    "function" === typeof value ||
    value instanceof Function);

export const memo = (Comp, resolver) => {
  if (isFunction(resolver)) {
    const Memoized = props => {
      const deps = resolver(props);
      if (deps && deps.length) {
        // eslint-disable-next-line react-hooks/rules-of-hooks
        return React.useCallback(React.createElement(Comp, props), deps);
      }

      return React.createElement(Comp, props);
    };

    Memoized.displayName = `memoized(${Comp.name})`;
    return Memoized;
  }

  return React.memo(Comp);
};

export default memo;

Here is how it will be used to compose components

import Todo from "./Todo";
import memo from "../memo";

export default memo(Todo, props => [props.text]);

I have a working codesandbox here — memo-deps

This is what I have observed —

  • I should not use React.useCallback or any hook inside a conditional statement because React needs to know the order in which hooks are invoked and using it inside a conditional may mess up the order during runtime
  • But React.useCallback works pretty neat in a conditional for my case as I know the order will remain the same during runtime
  • I am not using the hook inside the conditional statement during render, instead I am composing the component during export conditionally
  • I am thinking about React components as plain JavaScript functions and trying to memoize it like how I would memoize a regular JavaScript function
  • I could easily replace React.useCallback with lodash.memoize and the end result will be pretty much the same
  • I don't want to use an external library like lodash.memoize or build a custom implementation of memoization while React.useCallback pretty much does the work for me

This is where I am not sure what's happening (these are my questions) —

  • React components are not really vanilla JavaScript functions and I cannot memoize them with lodash.memoize
  • lodash.memoize and React.useCallback are not the same when I try to memoize a React component
  • React executes the function before figuring out the render even when React.memo is used (maybe to check prevProps vs newProps?)
  • Is my implementation okay even though it breaks the rules of React? (use hook in a conditional statement)
  • How else can I memoize a React.createElement if not for React.useCallback?

The reason as to why I might want to do this —

I don't want to memoize handlers (closure with a value and event) every time I pass them to a component wrapped in React.memo. I want to be able to declaratively write memoize dependencies for components.



from Can the useCallback React hook be used conditionally even if it breaks the rules of hooks?

No comments:

Post a Comment