Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 300
Dinesh Pandiyan Avatar asked May 07 '19 09:05

Dinesh Pandiyan


People also ask

Can React Hooks be called conditionally?

The error "React hook 'useState' is called conditionally" occurs when we use the useState hook conditionally or after a condition that may return a value. To solve the error, move all React hooks above any conditionals that may return a value.

Can Hooks be used conditionally?

Don't call Hooks inside loops, conditions, or nested functions. Instead, always use Hooks at the top level of your React function, before any early returns. By following this rule, you ensure that Hooks are called in the same order each time a component renders.

Why Hooks Cannot be called conditionally?

Hook functions should only be called in function-based components and in custom hooks. Hook functions should not be called conditionally. This means they should not be called in if/else blocks, loops, or nested functions. This ensures that for any component, the same hooks are invoked in the same order on every render.

For what purpose is the useCallback () hook used?

The useCallback hook is used when you have a component in which the child is rerendering again and again without need. Pass an inline callback and an array of dependencies. useCallback will return a memoized version of the callback that only changes if one of the dependencies has changed.


1 Answers

React.memo accepts a function as the second parameter to do a custom props comparison.

By default it will only shallowly compare complex objects in the props object. If you want control over the comparison, you can also provide a custom comparison function as the second argument.

You can use that in your util function like this :

export const memoWithSecondParam = (Comp, deps = []) => {
  return React.memo(Comp, (prevProps, nextProps) => {
    return deps.every(d => prevProps[d] === nextProps[d])
  });
};

And call it like this :

export default memoWithSecondParam(Todo, ["text"]);
like image 169
Mohamed Ramrami Avatar answered Sep 29 '22 23:09

Mohamed Ramrami