next.js: 'SomeComponent' cannot be used as a JSX component.

Verify canary release

  • I verified that the issue exists in the latest Next.js canary release

Provide environment information

    Operating System:
      Platform: win32
      Arch: x64
      Version: Windows 10 Pro
    Binaries:
      Node: 18.12.0
      npm: N/A
      Yarn: N/A
      pnpm: N/A
    Relevant packages:
      next: 13.0.1-canary.4
      eslint-config-next: 13.0.0
      react: 18.2.0
      react-dom: 18.2.0

What browser are you using? (if relevant)

No response

How are you deploying your application? (if relevant)

No response

Describe the Bug

Any components are producing IDE error but builds successfully.

'SomeComponent' cannot be used as a JSX component.
  Its return type 'Promise<Element>' is not a valid JSX element.
    Type 'Promise<Element>' is missing the following properties from type 'ReactElement<any, any>': type, props, key `ts(2786)`

Expected Behavior

probably not give an error when using async components

Link to reproduction

no link

To Reproduce

// ~/component/SomeComponent.tsx

export default async function SomeComponent() {
    let categories: any = await getCategories()
    return ...
}

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Reactions: 94
  • Comments: 108 (25 by maintainers)

Commits related to this issue

Most upvoted comments

I think a fix is upcoming, but pretty much the issue is that TypeScript doesn’t understand async components… It always expects JSX as child, not Promise<JSX>.

Many are type casting the component and exporting that type casted version… I think it might be better to do:

import { RSCList } from "../../../components/RSCList";

export default function Page({ params }: { params: { id: string } }) {
  return (
    <div>
      Order: {params.id}
      <div>
        {/* @ts-expect-error Server Component */}
        <RSCList />
      </div>
    </div>
  );
}

When the types are fixed, the @ts-expect-error directive will complain that it is unused…

This fix might come from the next typescript plugin I think, not sure.

Finally worked:

  1. Updated packages.
"@types/react": "^18.2.8",
"@types/react-dom": "^18.2.4",
"typescript": "^5.1.3"
  1. Nuked node_modules.
  2. Nuked package-lock.json
  3. Restarted Typescript & ESlist server.
  4. Sold my first born to the coding gods.
  5. Restarted VS Code.

No error. 😂

Just added a few notes about this workaround to the beta docs as well, thank you!

This particular error is hardcoded in TypeScript. The React team is working with the TypeScript team to resolve this. For now you can use the recommendation above 👍

how to write jsx now in a way that works with both? New builds now fail if i used ts-expect-error

You can remove all ts-expect-error related to this error now. If you’re using vscode make sure you’re using local version of it. image image

I’m on Next.js 13 and I’ve found two workarounds:

First one is using a const:

async function Page() {
    const asyncComponent: JSX.Element = await AsyncComponent({ props: any })
    return (
        <Suspense fallback={<>Loading...</>}>
            ......
            {asyncComponent}
            ......
        </Suspense>
    )
}

Second one is using {/* @ts-expect-error Server Component */}:

async function Page() {
    return (
        <Suspense fallback={<>Loading...</>}>
            ......
            {/* @ts-expect-error Server Component */}
            <AsyncComponent />
            ......
        </Suspense>
    )
}

I’ve confirmed this is working! 🎉

To use an async Server Component with TypeScript, ensure you are using TypeScript 5.1.3 or higher and @types/react 18.2.8 or higher, and select the correct TypeScript version inside of VSCode.

If you are still seeing issues, please try restarting VSCode or clearing node_modules.

Issue persisted.

Afaik, this is the PR that will fix it https://github.com/DefinitelyTyped/DefinitelyTyped/pull/65135 in combination with TypeScript 5.1. It should be released in a few days, with the official release of 5.1.2 TS coming tomorrow. I’ve managed to make it work locally by patching this PR content onto older version of react types - https://github.com/pawelblaszczyk5/coloruiz/blob/main/patches/%40types__react%4018.2.7.patch

Just upgraded to next 13.4.0, @types/react 18.2.5 @types/react-dom 18.2.3 in https://github.com/kachkaev/website/pull/32 – still seeing this error:

'MyAsyncComponent' cannot be used as a JSX component.
  Its return type 'Promise<Element>' is not a valid JSX element.
    Type 'Promise<Element>' is missing the following properties from type 'ReactElement<any, any>': type, props, key

Upgrading typescript 5.1.0-beta to typescript 5.1.0-dev.20230502 (latest available build) did not help.

The React team is working with the TypeScript team to resolve this.

Is there a public issue/PR on the TypeScript repo where that work can be followed?

I upgraded my package types: “@types/node”: “18.14.2”, “@types/react”: “18.0.28”, “@types/react-dom”: “18.0.11”, and it was resolved!

Didn’t work for me.

I upgraded my package types: “@types/node”: “18.14.2”, “@types/react”: “18.0.28”, “@types/react-dom”: “18.0.11”, and it was resolved!

The issue may have been introduced to your project via some other packages’ peer dependency.

If you have a .tsconfig file, Add following to compilerOptions:

“paths”: { “react”: [ “./node_modules/@types/react” ] }

The {/* @ts-expect-error Server Component */} get’s rid of the typescript error as mentioned by icyJoseph. However, This produces a different Server 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.. Unsure If I’m doing something wrong. I’m using a RSC in the /pages directory which is Promise<JSX.Element> and returns a list. The data to it is passed via gssp and props.

My guess is that the Promise is not resolved at the time of rendering. Hence, the error. If this is so, any workarounds until it is fixed.

Looks like typescript@5.1.3 (release notes) and @types/react@18.2.8 were released! 🎉

Maybe someone can confirm that this works out of the box now, and the issue can be closed?

Edit: Oh, @pawelblaszczyk5 thanks for confirming!

Now that https://github.com/microsoft/TypeScript/pull/51328 is merged and available in typescript@5.1.0-beta are there any additional changes needed for this to work without the {/* @ts-expect-error Server Component */} hack?

Example

// page.tsx
import React from 'react';
import MarkdownBlock from './markdown-block';

export default function Home(): React.ReactElement {
  return <MarkdownBlock markdown="hello" />;
}

// markdown-block.tsx
export default async function MarkdownBlock({
  markdown,
}: {
  markdown: string;
}): Promise<React.ReactElement> {
  const processedContent = await remark().use(html).process(markdown);

  return <span>{processedContent}</span>;
}

I upgraded typescript to the beta and still see the error:

'CodeSnippet' cannot be used as a JSX component.
  Its return type 'Promise<Element>' is not a valid JSX element.

I upgraded my package types: “@types/node”: “18.14.2”, “@types/react”: “18.0.28”, “@types/react-dom”: “18.0.11”, and it was resolved!

didn’t work either 😦

I’m using NextJS 13, the error still persists if you pass async server components. But {/* @ts-expect-error Server Component */} makes it work, would love to have a non-hacky way to approach this.

@icyJoseph

{/* @ts-expect-error Server Component */} worked for now.

Thanks

TypeScript 5.2 or higher

do you mean 5.1?

I have confirmed this is now working with

  • @types/react 18.2.8
  • typescript 5.1.3

The PR got merged and It’s working correctly with @types/react@18.2.8 and latest TypeScript version 🥳

@zackdotcomputer I tried that, but it didn’t change anything.

@srigi Another variation using the function keyword with a named export, note i have named both the variable and function the same to minimize refactoring when this TS issue is resolved.

export const Foo = async function Foo() {
  //...
} as unknown as () => JSX.Element

@sami616 genius, thanks! It can be simplified to…

type Props {
  foo: number;
}

const UserPosts = (async ({ foo }: Props) => {
  // some await fetch()

  return (
    <h2>some JSX</h2>
  );
}) as unknown as (props: Props) => JSX.Element;

export default UserPosts;

of if you prefer an even shorter classic function definition:

type Props {
  foo: number;
}

export default (async function UserPosts({ foo }: Props) {
  // some await fetch()

  return (
    <h2>some JSX</h2>
  );
} as unknown as (props: Props) => JSX.Element);

I just ran into this too–the recommendation to use as unknown as () => JSX.Element is what worked for me, here’s what I was doing:

some page

<HasPermission resource={Resource.EXAMPLE} action={Permission.CREATE}>
  <div>stuff</div>
</HasPermission>

@/util/HasPermission.tsx

'use server';

import React from 'react';
import { getSession } from './session';
import db, { Permission, Resource } from './db';

interface Props {
  children: React.ReactNode;
  resource: Resource;
  action: Permission;
}

const HasPermission = async function HasPermission(props: Props) {
  const session = await getSession();
  if (!session || !session.user || !session.user.email) {
    return null;
  }

  const permission = await db.userRolePermission.findFirst({
    where: {
      resource: props.resource,
      actions: {
        has: props.action,
      },
      Role: {
        is: {
          Users: {
            some: {
              email: session.user.email,
            },
          },
        },
      },
    },
  });

  if (!permission) {
    return null;
  }

  return <>{props.children}</>;
};

export default HasPermission as unknown as (props: Props) => JSX.Element;

I was happy to see at least here that in my call at the bottom, setting (props: Props) continued to let typescript understand what the required props were.

The problem with {/* @ts-expect-error Server Component */} is that it will silence other typing errors.

I updated the Typescript version in my project to 5.1.3 as well as @types/react to 18.2.8 suggested by @pawelblaszczyk5 but unfortunately the error still shows up in my case.

Starts at 18.2.4, 18.2.3 and lower are fine.

Seeing the same problem as @nmiddendorff in kachkaev/website#32. I thought that upgrading to typescript@5.1.0-beta, @types/react@18.2.1 and next@13.3.1 would help but it did not. What else is needed to remove @ts-expect-error before <ServerComponent />?

I am also having this issue. Upgrading didn’t help because type ElementType is not available in new packages?

I’ve done all the steps, yet still with the error of ‘xxx cannot be used as a JSX component’.

  • @types/react 18.2.8
  • typescript 5.1.3
  • select VS code ts version to typescript 5.1.3
  • remove node_modules and install again
  • restart VS code and computer

Yeah I’m still getting the issue Module '"react"' has no exported member 'cache'.

"@types/react": "18.2.8",
"@types/react-dom": "18.2.4",
"typescript": "5.1.3"

I faced a similar issue after upgrading @types/react from 18.2.0 to 18.2.6 alongside @types/react-dom from 18.2.1 to 18.2.4.

TL;DR: Try deduplicating @types/react in your lockfile, or remove and re-install @types/react-dom.

The underlying issue appeared to be that @types/react-dom lists @types/react@* as a dependency, so both @types/react@18.2.6 and @types/react-dom > @types/react@18.2.0 ended up being installed after the upgrade. This results in some sort of a type conflict and produces the error above.

@icyJoseph {/* @ts-expect-error Server Component */} saved me thanks.

{/* @ts-expect-error Server Component */}

Saved my day!

I worked around this one by writing a HOC. At least it wont shadow typing errors, keeps argument names and it’s less verbose to write compare to casting it multiple times.

type AFC<P = {}> = (
  ...args: Parameters<React.FC<P>>
) => Promise<ReturnType<React.FC<P>>>;

function asSyncComponent<T extends AFC<any>>(
  component: T
): React.FC<T extends AFC<infer P> ? P : never> {
  return component as any;
}
export const MyComponent = asSyncComponent(async function (props: any) {
  const data = await ...;
  return <>{...}</>;
});

Based on https://twitter.com/sebsilbermann/status/1664356039876124672 I created a new Next.js application using create-next-app and was able to verify it works now.

  • pnpm create next-app --typescript typescript-async-component
  • Changed app/page.tsx:
async function MyComponent() {
  await new Promise((resolve) => {
    setTimeout(resolve, 500);
  });

  return <h1>Hello World</h1>;
}

And added:

<div className={styles.center}>
  <MyComponent />
</div>

Great work @eps1lon!

In my opinion it makes sense for TypeScript to throw an error, after all this is an async function and may return a Promise. So, I would await it to return the JSX we are expecting:

The latest version of React (specifically for server components) supports async functions as JSX, TypeScript just wasn’t up to date with that yet.

Finally worked:

  1. Updated packages.
"@types/react": "^18.2.8",
"@types/react-dom": "^18.2.4",
"typescript": "^5.1.3"
  1. Nuked node_modules.
  2. Nuked package-lock.json
  3. Restarted Typescript & ESlist server.
  4. Sold my first born to the coding gods.
  5. Restarted VS Code.

No error. 😂

This guide along with this link fixed my issue - https://nextjs.org/docs/app/building-your-application/configuring/typescript

Similar solution here. In my case:

  1. Set new versions in package.json
"@types/react": "^18.2.8",
"@types/react-dom": "^18.2.4",
"typescript": "^5.1.3"
  1. Remove node_modules
  2. npm i
  3. > Developer: Reload window

And VSCode asked me to use the local typescript version instead of the global one. That did the trick.

The reoccurance of this error with @types/react 18.2.4+ is because of a mismatch in the typedefs between the one used internally by Next and the one your app uses if you are using Typescript 5. The types package added a TS5 specific typedef in 18.2.4 that isn’t present previously. If you downgrade to TS4 or the types package, it will fix the issue, but you can also force Next’s types to use the latest version in package.json to make the issue go away:

  "resolutions": {
    "@types/react-dom": "18.2.4",
    "@types/react": "18.2.5"
  }

(Edit: I see they’ve already updated the dependency in Next so the next release should also fix this.)

Same here, I am passing a SSC as a child to a CSC. Both are wrapped in a SSC as mentioned in the Nextjs Docs. Still I am getting the error “TS2786: ‘TopicCardContent’ cannot be used as a JSX component.”

<TopicCard side={"left"}><TopicCardContent /></TopicCard>

I solved this problem with following steps (if you are using VSCode):

Source: https://stackoverflow.com/a/65151679/3317931

image

Latest Typescript version has fixed this error. image

Reloading the window in VSCode might help. Worked from me. Also make sure @pawelblaszczyk5 suggestion about the workspace is set correctly.

@professorhaseeb or @leerob maybe the issue can be closed now?

doesn’t for me at least when props are passed down

edit: this did it, thanks export default OccurrencesTable as unknown as (props: OccurrencesTableProps) => JSX.Element; 👍

@hiendaovinh

Here is a wonky workaround which gets around the issue without losing all type safety for your incoming props. 👇🏼

type UserPostsProps = {
  userId: number
}

async function UserPosts({ userId }: UserPostsProps) {
  const userPosts: any = await fetch(
    `https://jsonplaceholder.typicode.com/posts?userId=${userId}`
  ).then(res => res.json())

  return (
    <ul>
      {userPosts.map((post: any) => (
        <li>{post.title}</li>
      ))}
    </ul>
  )
}

// As a temporary work around, we can override the type so that it returns a JSX.Element instead of a Promise 
const _UserPosts = UserPosts as unknown as (props: UserPostsProps) => JSX.Element
export { _UserPosts as UserPosts }

Don’t know why, but I am using app directory, but I can still see, 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. issue while rendering

However, This produces a different Server 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.. Unsure If I’m doing something wrong. I’m using a RSC in the /pages directory which is Promise<JSX.Element> and returns a list. The data to it is passed via gssp and props.

To my understanding server components are only available in the new app/ folder not pages/ so that would be the cause of your issue.

More background on the @emotion/react problem (fixed in @emotion/react@11.11.1):

Promise<React.ReactNode> seems to work just fine with latest types: Playground Link

The only bug we need to fix in types/react is that async is only for experimental when it’s already available in canary.

ReactNode now basically includes the Promise part atm, so your type is recursive and never ends smile If you really want to explicitly type it - using just a ReactNode should be fine

hmm, async function SomeComponent(): ReactNode { /** */ } results in: “The return type of an async function or method must be the global Promise type. Did you mean to write ‘Promise<string | number | boolean | ReactElement<any, string | JSXElementConstructor> | Iterable | ReactPortal | null | undefined>’? ts(1064)”

Oh, that’s strange, I’ve checked it now and I see this issue as well. Maybe it’s because the underlying type is using PromiseLike instead of native Promise interface 🤔 Because it seems like it gets stripped in proposed type

image

I personally don’t use explicit return types so I don’t have an idea for a good solution here, sorry. Though, it’s not related to this issue tbh, it’s just a typing problem

@timneutkens i am still seeing an error with an explicit Promise<ReactNode> return type annotation: “Type is referenced directly or indirectly in the fulfillment callback of its own ‘then’ method.ts(1062)”

see https://github.com/vercel/next.js/issues/42292#issuecomment-1575583207

Didn’t expect this to work, but nuking node_modules and pnpm-lock.yarn and re-installing actually fixed the issue.

I just confirmed that upgrading typescript packages and selecting the VSCode TS version fixed the issue. 🙌

how to write jsx now in a way that works with both? New builds now fail if i used ts-expect-error

You can remove all ts-expect-error related to this error now. If you’re using vscode make sure you’re using local version of it. image image

Unfortunately I can not do that. We are generating code for user to copy and paste into their app. We dont know which version of TS/react types will they have. If we remove ts-expect-error that will fail old versions. If we keep it we fail new versions. If we change it to ts-ignore we basically disable type checking. All bad options 😦

~doesn’t for me at least when props are passed down~

edit: this did it, thanks export default OccurrencesTable as unknown as (props: OccurrencesTableProps) => JSX.Element; 👍

https://github.com/vercel/next.js/issues/42292#issuecomment-1491127192

as unknown as () => JSX.Element works like a charm, thank you!

// ./app/components/Home/Logo.tsx
import Image from "next/image";

async function getData() {
  const res = await fetch('http://locahost/api/test');
  // The return value is *not* serialized
  // You can return Date, Map, Set, etc.
  if (!res.ok) {
    // This will activate the closest `error.js` Error Boundary
    throw new Error('Failed to fetch data');
  }
  return res.json();
}

export default (async function Logo() {
  const data = await getData();
  return (
    <div className="relative flex place-items-center before:absolute before:h-[300px] before:w-[480px] before:-translate-x-1/2 before:rounded-full before:bg-gradient-radial before:from-white before:to-transparent before:blur-2xl before:content-[''] after:absolute after:-z-20 after:h-[180px] after:w-[240px] after:translate-x-1/3 after:bg-gradient-conic after:from-sky-200 after:via-blue-200 after:blur-2xl after:content-[''] before:dark:bg-gradient-to-br before:dark:from-transparent before:dark:to-blue-700 before:dark:opacity-10 after:dark:from-sky-900 after:dark:via-[#0141ff] after:dark:opacity-40 before:lg:h-[360px]">
      <Image
        className="relative dark:drop-shadow-[0_0_0.3rem_#ffffff70] dark:invert"
        src="/img/next.svg"
        alt="Next.js Logo"
        width={180}
        height={37}
        priority
      />
      <p>{data.message}</p>
    </div>
  );
} as unknown as () => JSX.Element);

Although {/* @ts-expect-error Server Component */} also works, the earlier workaround remains the best.

I worked around this one by writing a HOC. At least it wont shadow typing errors, keeps argument names and it’s less verbose to write compare to casting it multiple times.

This works great. No need to fuss around with same pattern on every ServerComponent

Worth to note: If you put the {/* @ts-expect-error Server Component */} before the async JSX element, it will also ignore any other (unrelated) errors you might have. For example passing a prop but with a wrong type.

You only use async component inside app folder and server component.

However, This produces a different Server 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.. Unsure If I’m doing something wrong. I’m using a RSC in the /pages directory which is Promise<JSX.Element> and returns a list. The data to it is passed via gssp and props.

To my understanding server components are only available in the new app/ folder not pages/ so that would be the cause of your issue.

Ah Yes. I was not aware of it. In a sandbox I was able to reproduce the above error which I was expecting in /pages directory. Then I switched to beta /app directory and it worked charmingly. I now understand why it happened. Thanks @dantman!