Home > Enterprise >  useNameProps" is called conditionally. React Hooks must be called in the exact same order in ev
useNameProps" is called conditionally. React Hooks must be called in the exact same order in ev

Time:09-01

I have a React component that constantly gives me error: useNameProps" is called conditionally. React Hooks must be called in the exact same order in every component render.

The full code with mini repro is below:

import * as React from 'react';

function useNameProps(firstName: string, lastName: string): NameProps {
    return {
        name: firstName   lastName,
    };
}

function showShowName() {
    return false;
}

interface NameProps {
    name: string;
}

function Name(props: NameProps) {
    return <h1>Hello, {'hello'}</h1>;
}

export default function ComposeBottom() {
    return <div>{showShowName() && <Name {...useNameProps('Frank', 'Lee')} />}</div>;
}

My idea is to avoid calling useNameProps when showShowName() returns false as useNameProps is really expensive in my production code, so I have to make it behave conditionally, but it gave me the error above mentioned. I am wondering how to resolve this issue with the best practice.

Thanks in advance!

CodePudding user response:

Hooks must always be called exactly the same number of times, in exactly the same order. This is necessary because, for example, if you do multiple calls to useState, react must rely on the order of the calls to figure out which state should be returned from which invocation of useState. See rules of hooks.

As for how to fix this, there's a few possibilities:

  1. In your example, your custom hook doesn't actually call any other hooks. If that's true for the real code too, then you're getting a false alarm. The lint rule uses naming conventions to identify what is a hook and what is not, and functions starting with use are assumed to be hooks. To fix this, rename the function. Eg, function getNameProps(. But since i know you omitted code, i expect it probably does need to be a hook, and so this solution will not be for you.

  2. If it's not expensive, then just call it each time. You say it is expensive, so this solution is not for you, but i include it because it's the most common solution to this error message (because most hooks are lightweight)

export default function ComposeBottom() {
  const nameProps = useNameProps('Frank', 'Lee');
  return <div>{showShowName() && <Name {...nameProps} />}</div>;
}
  1. Modify useNameProps so you can pass something into it to tell it to not do the expensive computations. For example, maybe passing in null could tell it to do nothing. You will still need to call any hooks the same number of times, but you can skip the rest.
function useNameProps(options: UseNameOptions | null): NameProps | null {
  // If there are any hooks you are calling, they must be before you bail out
  const [foo, setFoo] = useState('bar')
  useEffect(() => {
    //...
  }, [])

  if (options === null) {
    return null;
  } 
  // else, do expensive stuff
}

export default function ComposeBottom() {
  const nameProps = useNameProps('Frank', 'Lee');
  return <div>{showShowName() && <Name {...nameProps} />}</div>;
}
  1. Move the usage of useNameProps into a child component, and don't render that component when you don't need it.
export default function ComposeBottom() {
    return <div>{showShowName() && <Child />}</div>;
}

function Child() {
  return <Name {...useNameProps('Frank', 'Lee')} />
}

CodePudding user response:

One possible approach would be to move the logic in setShowName into your useNameProps hook - and then have the now single hook conditionally return, say, null if the logic previously in setShowName not fulfilled.

function useNameProps(firstName: string, lastName: string): NameProps | null {
    if (/* insert logic here */) {
        return null;
    }
    // beginning of expensive logic
    return {
        name: firstName   lastName,
    };
}
export default function ComposeBottom() {
    const nameProps = useNameProps('Frank', 'Lee');
    return <div>{nameProps && <Name {...nameProps} />}</div>;
}
  • Related