React: Using Suspense & lazy triggers warning about functions are not valid as react child

Created on 24 Oct 2018  路  7Comments  路  Source: facebook/react

Do you want to request a feature or report a bug?
A bug.

What is the current behavior?
Using <Suspense /> & lazy to lazy load components triggers the following warning:

Warning: Functions are not valid as a React child. This may happen if you return a Component instead of <Component /> from render. Or maybe you meant to call this function rather than return it.

It does however not affect functionality at all and the lazy loading & rendering of the component works fine.

**If the current behavior is a bug, please provide the steps to reproduce and if possible a minimal demo of the problem.
https://codesandbox.io/s/mjzj27m0j8

What is the expected behavior?
Not trigger the warning as this should be a valid way of loading & rendering components according to the documentation.

Which versions of React, and which browser / OS are affected by this issue? Did this work in previous versions of React?
[email protected]
[email protected]
[email protected]

Most helpful comment

Hey @rovansteen! This is expected behavior, and the fix that @frehner provided is the correct way to do it. Suspense expects that the fallback prop will be a React element not a React component. When you do:

<Suspense fallback={() => <div>loading...</div>}>

You're passing in a function component that renders a div with some text. What you need to do is pass _what that function would return_ directly

<Suspense fallback={<div>loading...</div>}>

Which is why @frehner's change works because <Loading /> is a React element for the Loading component.

You can think of Suspense as working something like:

function Suspense({children, fallback}) {
  if (isSuspended) {
     // It just returns whatever `fallback` is when rendering, it doesn't do
     // <Fallback /> or anything like that.
     return fallback;
  } else {
    return children;
  }
}

So think of it just like you would children. What you're doing is equivalent to doing something like:

<Suspense>
  {() => <div>Loaded!</div>}
</Suspense>

Hopefully that makes it clear why React would warn about that function not being a valid child.

All 7 comments

I believe it's due to this line <Suspense fallback={() => <div>loading...</div>}>

If you pull out that function and make it a component, e.g.

const Loading = () => <div>loading...</div>;
...
<Suspense fallback={<Loading />}>

then the error appears to go away. At least as a temporary solution to this problem.

Hey @rovansteen! This is expected behavior, and the fix that @frehner provided is the correct way to do it. Suspense expects that the fallback prop will be a React element not a React component. When you do:

<Suspense fallback={() => <div>loading...</div>}>

You're passing in a function component that renders a div with some text. What you need to do is pass _what that function would return_ directly

<Suspense fallback={<div>loading...</div>}>

Which is why @frehner's change works because <Loading /> is a React element for the Loading component.

You can think of Suspense as working something like:

function Suspense({children, fallback}) {
  if (isSuspended) {
     // It just returns whatever `fallback` is when rendering, it doesn't do
     // <Fallback /> or anything like that.
     return fallback;
  } else {
    return children;
  }
}

So think of it just like you would children. What you're doing is equivalent to doing something like:

<Suspense>
  {() => <div>Loaded!</div>}
</Suspense>

Hopefully that makes it clear why React would warn about that function not being a valid child.

Thanks for the explanation @aweary. I misread the documentation that correctly describes returning the component directly rather than a function. I was just confused because it did not crash and the loading of the component was too fast to see or notice the fallback. Cheers!

@rovansteen thanks for bringing it up! We should definitely make sure this is explicitly called out in the docs as I assume it will be a common mistake, and its easy to miss.

Are there any plans to enable the use of a function as value for children? I have currently the case that I don't want to introduce another component for loading the data, so I can pass it down to the component where I need the data and then this would be really useful:

<Modal open>
  <Modal.Header>...</Modal.Header>
  <Modal.Body>
    <Suspense fallback={<p>Loading ...</p>}>
    {() => {
      const data = ModuleCache.read(id);

      return (
        <EditForm data={data} onChange={onChange} />
      );
    }}
    </Suspense>
  </Modal.Body>
</Modal>

Sure, I could also solve this problem with an IIFE, but to be honest: that's not the same as a native support by React itself.

@MeiKatz Me too ,could you resolve it now?

@MeiKatz Please file a new issue describing your use case and what exactly that helps you with. I don't think I understand it from a short description like this.

I'll lock this issue since it already attracts confusion (I don't know what @INTMIN's comment means). Please file new issues. Thanks.

Was this page helpful?
0 / 5 - 0 ratings