21

I've created a a higher order component that is supposed to add some additional functionality to my components. However, when I use react hooks in this component, I get the following eslint warning.

React Hook "React.useEffect" cannot be called inside a callback. React Hooks must be called in a React function component or a custom React Hook function. (react-hooks/rules-of-hooks)

Why am I getting this warning? Is it considered bad practice to use hooks in a HoC?

Minimal example:

const Hello = props => <p>Greetings {props.name}</p>;

const Wrapper = Component => props => {
  React.useEffect(() => {
    // Do something here
  }, []);
  return <Component {...props} />;
};

export default Wrapper(Hello)

codesandbox: https://codesandbox.io/s/proud-tree-5kscc

4
  • 1
    One of the complexities that hooks solve is the ability to share logic between components through custom hooks. refactor your HOC into a custom hook
    – Asaf Aviv
    Commented May 24, 2019 at 8:18
  • @AsafAviv Custom hooks don't solve all problems. One of the most powerful things in HOC components is the ability to return early and render different component, now try that with hooks.
    – RA.
    Commented Oct 30, 2019 at 18:20
  • @AsafAviv Isn't that what render props pattern is for?
    – PrashanD
    Commented Feb 9, 2020 at 18:25
  • @PrashanD It can be, but now you can just extract the state into a custom hook and use that hook in your components instead of render props
    – Asaf Aviv
    Commented Feb 10, 2020 at 2:55

6 Answers 6

19

Convert the

props => {
  React.useEffect(() => {
    // Do something here
  }, []);
  return <Component {...props} />;
};

inside your HOC to a function (react-hooks/rules-of-hooks is throwing that warning you showed when used in an arrow function returned by a HOC)

So, change it to

const Wrapper = Component =>
  function Comp(props) {
    React.useEffect(() => {
      console.log("useEffect");
    }, []);
    return <Component {...props} />;
  };

and the effect gets triggered.

Here is a working example on codesandbox

3
  • I always use the hooks this way in the functional components returned by a HOC.
    – s14k51
    Commented Sep 26, 2019 at 4:55
  • 1
    The returned function must not be a anonymous function。So ()=>{} and function (){} is not allowed, and function myComponent(){} is Ok.
    – George Lin
    Commented Nov 7, 2019 at 2:08
  • Why is const Wrapper = Component => props => { incorrect? I have been doing that a lot to convert hooks to HOCs. Have I been doing it wrong this whole time. Is this just for the linting rule? Commented Jan 16, 2020 at 20:41
2

The official React Hooks documentation says:

Don’t call Hooks from regular JavaScript functions. Instead, you can:

✅ Call Hooks from React function components.

✅ Call Hooks from custom Hooks.

As @AsafAviv said, you should refactor your HOC into a custom hook to avoid violation the Rules of Hooks.


The reason is described in the FAQ by the way:

How does React associate Hook calls with components?

React keeps track of the currently rendering component. Thanks to the Rules of Hooks, we know that Hooks are only called from React components (or custom Hooks — which are also only called from React components).

There is an internal list of “memory cells” associated with each component. They’re just JavaScript objects where we can put some data. When you call a Hook like useState(), it reads the current cell (or initializes it during the first render), and then moves the pointer to the next one. This is how multiple useState() calls each get independent local state.

4
  • 10
    I get that the Rules of Hooks make sure that react hooks are only called from inside React components. But this function does indeed return a React component, so I don't see the issue. Isn't the linting rule just too broad? Commented May 25, 2019 at 15:13
  • Can you also add a solution, in this case? Commented Aug 2, 2020 at 13:49
  • 1
    It's got to be a bug in the rule.. it's not complaining when the inner function is named, which should not make any difference.
    – riv
    Commented Aug 10, 2020 at 21:58
  • Apparently, the error is valid. github.com/facebook/create-react-app/issues/7033 I just don't agree with it. Commented Dec 7, 2020 at 17:47
1

You can use react hooks in the functional components or in the custom Hooks. rewrite your HOC:

const Hello = props => <p>Greetings {props.name}</p>;

const HookDoSomething = () => {
  React.useEffect(() => {
    // Do something here
  }, []);
}

const Wrapper = Component => props => {
  HookDoSomething()
  return <Component {...props} />;
};

export default Wrapper(Hello)
1

Inside the file where you have your HoC defined, simply add the following to the top of the file:

/* eslint-disable react-hooks/rules-of-hooks */

Hooks and higher-order components are two completely different things. Anyone who says a HoC can be replaced by a hook has either never actually written a HoC or playing semantics games.

When I write a HoC, I often have to disable the rules-of-hooks eslint rule because the rule is too stringent wrt what it thinks is a hook or component. HoC is more akin to a component than a hook, but the rule does not recognize this.

1
  • yes it's really annoying
    – Normal
    Commented Sep 6, 2022 at 10:39
0

Short Answer: You just need to change the callback to a PascalCase function or useSomething function. This is because eslint rule has some heuristic that you need to follow in order for it to detect components.

You will need to change your code to

const Wrapper = Component => {
  return function WithWrapper(props){
    React.useEffect(() => {
        // Do something here
      }, []);
    return <Component {...props} />;
  }
}
0

just change the name to lower case like :

withWrrapperHOC = Comp => props => {
 useEffect(()=>{
  //...
 },[])
 return (<Comp {...props}>)
}

Not the answer you're looking for? Browse other questions tagged or ask your own question.