Home > Software engineering >  React avoid calling Hook conditionally
React avoid calling Hook conditionally

Time:09-17

I have a component that needs to call a custom React hook only when certain prop is passed. Like this:

import { useCustomHook } from './myHook';

export function Chart(props) {

   ...
   {lots of code here}
   ...

   if (props.canTrack) {
        useCustomHook();
   }
   return <SomeComponent options={props.option} />
}

This is complaining with: error React Hook "useCustomHook" is called conditionally. React Hooks must be called in the exact same order in every component render. Did you accidentally call a React Hook after an early return?

I was able to quickly go around this by duplicating the Chart component into 2 Components:

import { useCustomHook } from './myHook';

export function Chart(props) {

   ...
   {lots of code here}
   ...

   return <SomeComponent options={props.option} />
}

export function TrackedChart(props) {

   ...
   {lots of code here}
   ...

   useCustomHook();
   return <SomeComponent options={props.option} />
}

Which works but now I got duplicated code which is highly undesirable. In an OOP language I could use inheritance, what is the correct approach in React to avoid duplicating code or even better, be able to safely call hooks conditionally.

CodePudding user response:

Hard to give specific recommendations without knowing what the code is, but some possible fixes are:

  1. Write useCustomHook in a way so it can skip its work if you pass in a parameter. Then always call the hook, but change that parameter based on the prop. This is what I do most of the time when faced with a case like this.
export function Chart(props) {

   ...
   {lots of code here}
   ...

   useCustomHook(props.canTrack)
   return <SomeComponent options={props.option} />
}
  1. Split the component as you've done, but extract the common code to a helper function (if hooks are called in {lots of code here}, then that helper function will be a hook itself)
function useLotsOfCode() {
   ...
   {lots of code here}
   ...

   // Maybe return stuff if needed
}

export function Chart(props) {
   useLotsOfCode();
   return <SomeComponent options={props.option} />
}

export function TrackedChart(props) {
   useLotsOfCode();
   useCustomHook();
   return <SomeComponent options={props.option} />
}
  1. Split the component, but do it on a smaller scale. A parent component does all the shared code, and then you just do the split where you need the extra hook
export function Chart(props) {

   ...
   {lots of code here}
   ...
   if (props.canTrack) {
     return <WithTheHook />
   } else {
     return <WithoutTheHook />
   }
}

function WithTheHook () {
  useCustomHook();
  return <SomeComponent options={props.option} />
}

function WithoutTheHook() {
  return <SomeComponent options={props.option} />
}
  • Related