Nextjs Error: Objects are not valid as a React child (found: [object Promise]). If you meant to render a collection of children, use an array instead

I've noticed within the community of nextjs, there's a repetitive error that keeps occurring but is very difficult to understand.

if you get this error, here are a few reasons why this error might occur

This error hides another error

In nextjs 13, in "app" folder approach. you might get this error:

Error: Objects are not valid as a React child (found: [object Promise]). If you meant to render a collection of children, use an array instead.

but behind the scenes the reason for that error is another error. and that error is the one that raise the "objects are not valid as a react child". to see the hidden error, add the "error.tsx" entity file:

// error.tsx entity file
'use client';

import { useEffect } from 'react';

export default function Error({
    error,
    reset,
}: {
    error: Error;
    reset: () => void;
}) {
    useEffect(() => {
        console.error(error);
    }, [error]);

    return (
        <main>
            <h2>Something went wrong!</h2>
        </main>
    );
}
  1. This error capture in the UI will allow you to focus on the real error.

  2. don't forget to add the above file to whatever folder/sub-folder that might have the issue.

  3. a common error, in this case, is a server-side error like failing fetch that won't be visible in the front end(in the browser) and in some cases might be hidden even in the terminal which makes it hard to detect!

This error happens because mis-match server to front end - hooks are not allowed in react server component!

using use client or async page components can lead to problems! here's where it gose wrong:

// page.tsx entity file
"use client" // using "use client" on a page.tsx entity file is bad idea!

// it's ok to do a async fetch, this is possible both on front end and backend:
async function fetchData(promptslug:string) { 
    let res = await fetch(`http://localhost:3000/api/data`, {
        method: "GET",
        headers: {
            "Content-Type": "application/json",
            'Accept': 'application/json'
        },
    });

    if(!res.ok) {
        throw new Error('Fail fetch');
    }

    return res.json();
}


// usage of async on a page component
export default async function Page() {
    const data = await fetchData();

    useEffect(() => {
        // usage of useEffect! its a client behaviour - it will fail since this "page.tsx" entity which is server side component!
    }, []);

    return (<div className="main">
        <h1>Hello World</h1>
    </div>
  )
}

How To fix Above problem?

// profile/page.tsx entity file
// it's ok to do a async fetch, this is possible both on front end and backend:
async function fetchData(promptslug:string) { 
    let res = await fetch(`http://localhost:3000/api/data`, {
        method: "GET",
        headers: {
            "Content-Type": "application/json",
            'Accept': 'application/json'
        },
    });

    if(!res.ok) {
        throw new Error('Fail fetch');
    }

    return res.json();
}


// usage of async on a page component
export default async function Page() {
    const data = await fetchData();

    return (<div className="main">
        <h1>Hello World</h1>
    </div>
  )
}

// --------------------- a new file ---------------------

// create another file on the same folder but for the front end!
// profile/someFrontEndComponent.tsx - regular component file (not entity file)

"use client" // its ok to use use client. this component is for FE!
export default function SomeForntEndComponent() {
    useEffect(() => {
        // usage of useEffect! its a client behaviour!
    }, []);

    return (<div className="main">
        <h1>Sub component = its fe behaviour</h1>
    </div>
  )
}

See how we split the front end from the entity file "page.tsx" to it's sub-component that is front end component and able to import useEffect, use state and other FE like behaviors! good separation of concerns between server side behaviour and rendering to front end side rendering and behavior (rather than doing window === undefined etc.)

Layout, Template, page - which server which front end?

Let's find the best practice to avoid problems, let's take a look at below examples.

layout should have "use client" at the top with all relevant proviers. since providers are react context, this part is not server side react. which is fine.

// layout.tsx entity file (single file, no other layouts)
"use client" // front end friendly component. the sub-component will be rendered at the server side. e.g the template.tsx

import { SessionProvider } from "next-auth/react"

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body>
      <SessionProvider> {/** as of nextjs v13 no need session={pageProps.session} */}
        {children}
        </SessionProvider> {/* its ok to use provider, this component is "use client" */}
        </body>
    </html>
  )
}

however, in the template. we can avoid "use client" and avoid "use client" or anything that is front-end like: hooks (use effect, use state) and on the template we can structure our app and fetch data server side with async function and

// template.tsx - entity file (top level)
import HeaderComponent from './components/Header';
import Slider from './components/Slider';
import Footer from './components/Footer';
import Sidebar from './components/Sidebar';

export default async function Template({ children }: {
    children: React.ReactNode
  }) {

    return <div className="container-fluid">
        <Slider/>
        <HeaderComponent />
        {children}
        <Footer/>
  </div>;
  }

How to test Front end Or Server Side rendering?

inside your chrome dev tool go to "3 dots" (at the top right) and find "disable javascript" - click on checkbox (don't forget to uncheck) and refresh the page to see what's rendered on the server side and what render on the client side - happy debugging!