Calling a React hook conditionally

Viewed 20955

From the react official documentation we know that "React relies on the order in which Hooks are called". So is there anything wrong with "reserving" a spot for a hook if I want to call it conditionally?

function Component({flag, depA, depB}) {

  if (flag) {
    // just "reserving a spot"
    useEffect(() => {}, [null, null])
  } else {
    useEffect(() => {
      // ... actual hook
    }, [depA, depB])
  }

  return <></>
}

If this works, would it also work for useCallback? useLayoutEffect? useMemo? useImperativeHandle?

I've tested all of this and in much more complicated contexts, it seems to work even though the linter complains. Am I missing something?

PS: if it looks kind of useless just like this, it's because the end goal is to have the main part of the hook be lazy loaded with import(), and before the import is triggered and resolved, just reserve the spots for hooks.

3 Answers

You cannot call useEffect conditionally as that is breaking the rules of hooks instead you could do the following:

  useEffect(() => {
    if (flag) {
      console.log("do something");
    } else {
      console.log("do something else");
    }
  }, [depA, depB]);

I was just thinking about this today. I believe that while it 'breaks the rules', there's nothing that React could do to tell the difference between the two.

So while it breaks the rules, if you have a good enough reason, understand the risks, then the 'rules' is just dogma.

React basically knows which useEffect hook is which, basically by counting invocations. Calling useEffect conditionally is bad, specifically because the amount of times useEffect gets called cannot change.

Your example is conditional, but React can't detect it because in either condition you call it once.

However, the example you mention seems like it doesn't need this. There's good reasons to do things the 'normal' way, because as you can see from other commenters here, it causes confusion and surprise, and we don't like surprise =)

If you are lazily loading in some functionality, just have your useEffect hook call the function when it is ready.

My solution is like this, and it works for all hooks, however it's better to use it when you have to since it can cause confusion and surprise

const ConditionalEffect1 = () => {
  useEffect(() => {}, [null, null])
  return null
}

const ConditionalEffect2 = ({depA, depB}) => {
  useEffect(() => {}, [depA, depB])
  return null
}

const Component = ({ flag, depA, depB }) => {
  return flag 
          ? <ConditionalEffect1 /> 
          : <ConditionalEffect2 depA={depA} depB={depB} />
}

Related