Today I Learned

How to make a hook run conditionally

Rules of hooks clearly state that hooks cannot exist inside conditional code. However there is a way to do this.

Example

const MyComponent = ({isLoading}) => {
  useEffect(() => {
    if (!isLoading) { // <= condition is inside hook
      // some behavior
    }
  }, [isLoading])

  const someCalculationResult = useMemo(() => {
    if (!isLoading) { // <= AGAIN condition is inside hook
      return calculationResult // perform calculation
    }
    return undefined
  }, [isLoading])

  if (isLoading) return <LoadingSpinner />

  return (
    <div>{someCalculationResult}</div>
  )
}

The hooks are begging to be put after if (isLoading) return <LoadingSpinner /> so there is no need to add if(!isLoading) conditions inside them.

The way to trigger hooks conditionally only when isLoading is false is to wrap them in component rendered conditionally.

const ConditionalHookWrapper = ({children}) => {
  // no need to assert !isLoading inside hooks
  useEffect(() => {
    // some behavior
  }, [])

  const someCalculationResult = useMemo(() => {
    return calculationResult // perform calculation
  }, [])
  
  return children({someCalculationResult})
}

const MyComponent = ({isLoading}) => {
  if (isLoading) return <LoadingSpinner />

  // ConditionalHookWrapper component is rendered only when isLoading is false
  return (
    <ConditionalHookWrapper>
      {({ someCalculationResult }) => (
        <div>{someCalculationResult}</div>
      )}
    </ConditionalHookWrapper>
  )
}

In this example is shown additional situation, when we need to get some data from the conditional component. If that would not be the case then

const ConditionalHookWrapper = () => {
  // no need to assert !isLoading inside hooks
  useEffect(() => {
    // some behavior
  }, [])

  return null
}

const MyComponent = ({isLoading}) => {
  const [someValue, setSomeValue] = useState()

  if (isLoading) return <LoadingSpinner />

  return (
    <>
      <ConditionalHookWrapper/>
      <div>{someValue}</div>
    </>
  )
}