skip to Main Content

I know this error has been seen many times:

Rendered more hooks than during the previous render.

From what I read, it’s to do with an early return statement. Well I’m creating a library for our company where I wrap a form and allow them to pass their own layout. Initially the form isn’t ready to render their form (depending on a few factors), but eventually it’s ready and should render. That creates a conditional render, and returns me that error.

What I don’t understand is if I explicitly check a boolean value to determine if it’s ready, I am not rendering anything where there will be an invalid number of hooks.

I’ve created a simplified example of the problem/error below. Here I check if it’s "ready" (yes, I’m just doing this in a useEffect but it’s more complicated than this, but the result is the same). I then conditionally check if we’re ready to render their layout (which may or may not contain hooks – which gives the error), or I’d like to return a default component message.

From reading I MUST render this dynamic component regardless or look to pass a prop down to the child, so the consumer has to check if it’s ready to then do their layout – and that’s nasty.

Is this really the case or am I missing something simple? Can I disable the error knowing myself it’s impossible for it until a certain state is ready?

This will produce the error:

export function ParentComponent({childComponent}) {

    const [ready, setReady] = useState(false);
    useEffect(() => {
        console.log("Mounted, now ready");
        setReady(true);
    }, []);

    if (ready)
        // This is what a user passes, so can be any component which can use hooks, as per my example
        return childComponent();

    return <p>Not ready yet.</p>
}

export function childComponent() {

    // Important, this breaks it
    const [someStateValue, setSomeStateValue] = useState(false);

    return <p>Hello: {someStateValue}</p>;
}

Usage:

return <ParentComponent childComponent={childComponent}></ParentComponent>

2

Answers


  1. am I missing something simple?

    Yes. If childComponent is a component, you should render it like a component. That way, any hooks that the childComponent function calls will be associated with that as a separate component instead of being associated with your parent component. By calling it like a normal function, React doesn’t have a way to tell that the hooks from the child are not supposed to be "part of" your component.

    export function ParentComponent({
     childComponent: ChildComponent
    }) {
    
      ...
    
      if (ready) {
        return <ChildComponent/>
      } else {
        return <p>Not ready yet.</p>
      }
    }
    

    Note the childComponent: ChildComponent that I changed from your original post. I’m not 100% sure offhand, but I think this will be necessary so that React doesn’t try to do e.g. a document.createElement('childComponent'). You’d still use the prop as <ParentComponent childComponent={childComponent} />

    Login or Signup to reply.
  2. When you call a component as a normal function myComponent() it is not the same to React as using it as JSX (or React.createElement(myComponent)). Without this, the component will not get it’s own component lifecycle (mount, unmount, etc) and you cannot use hooks reliably.

    You can change it to use JSX and capitalize the component name to allow hooks to be used:

    export function ParentComponent({childComponent}) {
    
        const [ready, setReady] = useState(false);
        useEffect(() => {
            console.log("Mounted, now ready");
            setReady(true);
        }, []);
    
        if (ready)
            // This is what a user passes, so can be any component which can use hooks, as per my example
            const ChildComponent = childComponent;
            return <ChildComponent />
    
        return <p>Not ready yet.</p>
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search