react-email icon indicating copy to clipboard operation
react-email copied to clipboard

Objects are not valid as a React child Error when sending react-email with resend

Open wtkw opened this issue 1 year ago • 25 comments

Describe the Bug

Sending an email with resend using react component throws a following error: [Error: Objects are not valid as a React child (found: object with keys {$$typeof, type, key, ref, props, _owner, _store}). If you meant to render a collection of children, use an array instead.]

It used to work in the past, but some updates (react-email, resend or react) must have broken it.

If sent as a HTML string, it works.

Which package is affected (leave empty if unsure)

react-email

Link to the code that reproduces this issue

private repo

To Reproduce

Sending an email with resend using react component:

await resend.emails.send({
      to: email,
      from: 'Page <[email protected]>',
      subject: 'Verify your account',
      react: VerifyLinkEmail({
        token,
        url,
      }),
    })

throws a following error: [Error: Objects are not valid as a React child (found: object with keys {$$typeof, type, key, ref, props, _owner, _store}). If you meant to render a collection of children, use an array instead.]

The E-Mail is this:

const VerifyLinkEmail = ({ token, url }: VerifyLinkEmailProps) => {
  return (
    <Html>
      <Head />
      <Preview>Confirm the e-mail to register.</Preview>
      <Tailwind>
        <Body className="my-12 bg-white font-sans">
          <Container className="mx-auto w-[400px] border px-4 py-10 text-center shadow-md">
            <Section className="my-12">
              <Img
                src="https://link/logo_plain.png"
                className="mx-auto h-16 w-16"
              />
              <Heading as="h1" className="text-3xl text-blue-600">
                Page Name
              </Heading>
            </Section>
            <Text>
              To finish the registration process, click the button below
            </Text>
            <Button
              href={`${url}/auth/verify?token=${token}`}
              className="w-24 rounded-md bg-blue-600 px-4 py-3 font-mono tracking-widest text-white"
            >
              VERIFY
            </Button>
            <Text>Link is only valid for one hour</Text>
            <Section className="mt-6">
              <Text className="text-xs">
                If you didn&apos;t try to login, you can safely ignore this
                email.
              </Text>
            </Section>
          </Container>
        </Body>
      </Tailwind>
    </Html>
  )
}

package.json:

   "dependencies": {
    "@react-email/components": "^0.0.29",
    "next": "15.0.3",
    "react": "18.3.1",
    "react-dom": "18.3.1",
    "resend": "^4.0.1",
    "typescript": "5.6.3",
  }

If sent as a HTML string, it works:

await resend.emails.send({
      to: email,
      from: 'Page <[email protected]>',
      subject: 'Verify your account',
      html: '<p>Test</p>'
    })

Expected Behavior

Email sent without an error.

What's your node version? (if relevant)

22.11.0

wtkw avatar Dec 06 '24 05:12 wtkw

Hi @wtkw do you have a setup for the code anywhere to check?

Shriom-Trivedi avatar Dec 07 '24 10:12 Shriom-Trivedi

Hi @wtkw

It worked when I passed the component like this <Component /> instead of Component.

So maybe you can try this.

await resend.emails.send({
      to: email,
      from: 'Page <[email protected]>',
      subject: 'Verify your account',
      react: <VerifyLinkEmail token={token} url={url}/>,
    })

Shriom-Trivedi avatar Dec 07 '24 10:12 Shriom-Trivedi

Despite to what the documentation says I can only pass it as a function. There is no way I can import it as a Component I think because it is a part of the NextJS POST API function which runs on a server.

Hi @wtkw do you have a setup for the code anywhere to check?

I have it on my private repo, as a part of the authentication route. I could reproduce parts of it on public repo somehow (both backend, frontend, prisma, etc.) but before that I can maybe try to give some more context if this would help?

wtkw avatar Dec 07 '24 13:12 wtkw

As mentioned, it is a POST function on NextJS API sign-up route, which gets user's registration data from a form in the frontend:

import { NextResponse } from 'next/server'
import prisma from '@/src/lib/prisma'
import bcrypt from 'bcrypt'
import { resend } from '@/src/lib/resend'
import VerifyLinkEmail from '@/src/lib/email-templates/VerificationEmail'
import jwt, { Secret } from 'jsonwebtoken'
import { getBaseUrl } from '@/src/utils/utils'

export async function POST(req: Request) {
  const body = await req.json()
  const { name, email, password, confirmPassword } = body

  // * Check if user exists and validate input data
  try {
    const existingUser = await prisma.user.findUnique({
      where: {
        email,
      },
    })

    if (existingUser) {
      return NextResponse.json(
        {
          message:
            'User with this e-mail already exists. Did you forget, you have an account?',
        },
        { status: 422 },
      )
    }

    if (password !== confirmPassword) {
      return NextResponse.json(
        { message: 'Repeated password incorrect.' },
        { status: 422 },
      )
    }
  } catch (error) {
    console.log('[AUTH_SIGNUP_POST]', error)
    return NextResponse.json(
      { message: 'Internal authentication error. Try again later.' },
      { status: 500 },
    )
  }

  // * Hash verification code and send verification email
  try {
    const token = "generatedToken"
    const url = getBaseUrl()

    await resend.emails.send({
      to: email,
      from: 'Page <[email protected]>',
      subject: 'Verify your account',
      react: VerifyLinkEmail({
         token,
         url,
      }),
    })
  } catch (error) {
    console.log('[VERIFICATION_EMAIL]', error)
    return NextResponse.json(
      { message: 'Verification e-mail not sent. Try again later.' },
      { status: 500 },
    )
  }

  // * Create new user
  try {
    const hashedPassword = await bcrypt.hash(body.password, 12)

    const newUser = await prisma.user.create({
      data: {
        email,
        hashedPassword,
        name,
        emailVerified: null,
      },
    })

    return NextResponse.json(newUser)
  } catch (error) {
    console.log('[AUTH_SIGNUP_POST]', error)
    return NextResponse.json(
      { message: 'Verification e-mail not sent. Try again later.' },
      { status: 500 },
    )
  }
}

after sending the email with a verification token, user is created in the DB to verify and activate the account.

It used to work flawlessly until something got updated (either react or nextjs or resend) and I did not catch it on time with teesting...

wtkw avatar Dec 07 '24 17:12 wtkw

happening with me on react-email package:

Error: Objects are not valid as a React child (found: object with keys {$$typeof, type, key, props, _owner, _store}). If you meant to render a collection of children, use an array instead.

image

dBianchii avatar Dec 09 '24 11:12 dBianchii

@wtkw interesting... could you also tell me how you are using the <VerifyLink /> component and its setup. Would love to see what is going on in here.

Shriom-Trivedi avatar Dec 09 '24 12:12 Shriom-Trivedi

@dBianchii could you also share the usecase that you are using?

Shriom-Trivedi avatar Dec 09 '24 12:12 Shriom-Trivedi

@dBianchii could you also share the usecase that you are using?

Sure. I am just using the react-email package so I can preview my emails. I think sending emails on my backend is working fine

BTW, I am on React19

import { Body, Head, Html, Preview, Tailwind } from "@react-email/components";

export default function WarnDelayedCriticalTasks({
  taskTitle = "Comer macarrĂŁo",
  // taskDate = new Date(),
}: {
  taskTitle: string | null;
  // taskDate: Date;
}) {
  return (
    <Html>
      <Head />
      <Preview>{`A tarefa ${taskTitle} está atrasada`}</Preview>
      <Tailwind>
        <Body className="mx-auto my-auto bg-white font-sans">
         Just testing
        </Body>
      </Tailwind>
    </Html>
  );
}

dBianchii avatar Dec 09 '24 12:12 dBianchii

@dBianchii that issue seems to be unrelated, but it does mean that there are multiple React versions running at the same time in your case because the element structure changed from React 18 to 19

gabrielmfern avatar Dec 09 '24 17:12 gabrielmfern

@wtkw interesting... could you also tell me how you are using the component and its setup. Would love to see what is going on in here.

The user register form triggers an onSubmit function:

const onSubmit: SubmitHandler<Inputs> = async (values) => {
    try {
      setIsLoading(true)
      await axios.post('/api/auth/signup', values)
      toast({
        variant: 'default',
        title: 'User registered successfully',
        description: 'Check your email to verify your account',
        duration: 9000,
      })
      router.push(`/auth/verify?email=${values.email}`)
    } catch (error: unknown) {
      if (error instanceof AxiosError) {
        toast({
          variant: 'destructive',
          title: 'Error ' + error.response?.status,
          description: error.response?.data?.message,
          duration: 9000,
        })
      } 
    } finally {
      form.reset()
      setIsLoading(false)
    }
  }

which uses axios (it was written before Server Actions in NextJS became a thing) to send the POST request I pasted above. POST request triggers a serverside function to send an email with resend.

The e-mail is not send correctly due to above mentioned error, so the error response is presented back on the frontend.

Please let me know what other exact information would you need?

wtkw avatar Dec 10 '24 14:12 wtkw

I have this workaround on my end.

import { render } from '@react-email/components';
....
  const content = await render(MyComponent(props));

  await resend.emails.send({
    from: EmailFrom,
    to: emails,
    subject,
    html: content,
  });

Keep getting the error in this format:

import { render } from '@react-email/components';
....
  await resend.emails.send({
    from: EmailFrom,
    to: emails,
    subject,
    react: MyComponent(props),
  });

emroot avatar Dec 11 '24 20:12 emroot

I also am seeing this error on the dev server with React 19. Pretty basic repro steps here:

{
  "name": "email",
  "version": "1.0.0",
  "type": "module",
  "scripts": {
    "dev": "email dev --port 3030"
  },
  "devDependencies": {
    "react-email": "^3.0.4"
  },
  "dependencies": {
    "@react-email/components": "^0.0.31",
    "react": "^19.0.0",
    "react-dom": "^19.0.0"
  }
}
import { Link, Section, Text, Button } from "@react-email/components";
import React from "react";

const InviteEmail = () => (
<Html>
  <Body
    <Text style={{ marginBottom: "24px" }}>
      Someone invited you to join Example.
      To get started, open the{" "}
      <Link
        href="https://example.com/dashboard"
        target="_blank"
      >
        Example dashboard
      </Link>{" "}
      and log in:
    </Text>

    <Section style={{ marginTop: "12px", marginBottom: "32px" }}>
      <Button href="https://example.com/login">Log in to Example.com</Button>
    </Section>

    <Text className="mb-4 text-gray-800">
      If you have any questions, feel free to{" "}
      <Link
        href="mailto:[email protected]"
        style={{
          textDecoration: "underline",
        }}
      >
        contact us
      </Link>{" "}
      anytime.
    </Text>
  </Body>
</Html>
);

export default InviteEmail;

hexcowboy avatar Dec 12 '24 14:12 hexcowboy

I have this workaround on my end.

Tried that but the render function throws following:

[VERIFICATION_EMAIL] Error: Objects are not valid as a React child (found: object with keys {$$typeof, type, key, props, _owner, _store}). If you meant to render a collection of children, use an array instead.
    at renderNodeDestructiveImpl (C:\Users\User\Projects\projects\page\node_modules\.pnpm\[email protected][email protected]\node_modules\react-dom\cjs\react-dom-server.node.development.js:6256:11)
    at renderNodeDestructive (C:\Users\User\Projects\projects\page\node_modules\.pnpm\[email protected][email protected]\node_modules\react-dom\cjs\react-dom-server.node.development.js:6153:14)
    at renderNode (C:\Users\User\Projects\projects\page\node_modules\.pnpm\[email protected][email protected]\node_modules\react-dom\cjs\react-dom-server.node.development.js:6336:12)
    at renderSuspenseBoundary (C:\Users\User\Projects\projects\page\node_modules\.pnpm\[email protected][email protected]\node_modules\react-dom\cjs\react-dom-server.node.development.js:5657:5)
    at renderElement (C:\Users\User\Projects\projects\page\node_modules\.pnpm\[email protected][email protected]\node_modules\react-dom\cjs\react-dom-server.node.development.js:6071:11)
    at renderNodeDestructiveImpl (C:\Users\User\Projects\projects\page\node_modules\.pnpm\[email protected][email protected]\node_modules\react-dom\cjs\react-dom-server.node.development.js:6181:11)
    at renderNodeDestructive (C:\Users\User\Projects\projects\page\node_modules\.pnpm\[email protected][email protected]\node_modules\react-dom\cjs\react-dom-server.node.development.js:6153:14)
    at retryTask (C:\Users\User\Projects\projects\page\node_modules\.pnpm\[email protected][email protected]\node_modules\react-dom\cjs\react-dom-server.node.development.js:6605:5)
    at performWork (C:\Users\User\Projects\projects\page\node_modules\.pnpm\[email protected][email protected]\node_modules\react-dom\cjs\react-dom-server.node.development.js:6653:7)
    at Immediate.<anonymous> (C:\Users\User\Projects\projects\page\node_modules\.pnpm\[email protected][email protected]\node_modules\react-dom\cjs\react-dom-server.node.development.js:6980:12)

wtkw avatar Dec 12 '24 15:12 wtkw

I have the same issue.

tecoad avatar Dec 12 '24 19:12 tecoad

I just created a Stack Overflow question for that here... Im having the same issue with Nodemailer, so I don't think it's related to resend...

rrcabrera9625 avatar Dec 12 '24 21:12 rrcabrera9625

Hello Folks!

I just got it working. I simply switched back to using renderAsync (instead of render) with the react-email/[email protected] version. It seems the "new" render has some issue that I haven't been able to identify yet.

Here's a working code example:

import { renderAsync } from 'npm:@react-email/[email protected]'
import { ReservationConfirmEmail } from './_templates/reservation-code.tsx'

// ...

const html = await renderAsync(React.createElement(ReservationConfirmEmail))

// ... Send html via Nodemailer

Result: Email sent successfully with the template rendered correctly.

rrcabrera9625 avatar Dec 13 '24 16:12 rrcabrera9625

Can anyone here make a minimal reproduction of this? With or without the Resend SDK?

gabrielmfern avatar Dec 13 '24 17:12 gabrielmfern

I think the issue is with conflicting @babel/core. If you run npm list @babel/core, you'll see something like this:

@remix-run/[email protected]
│   ├─┬ @babel/[email protected] invalid: "7.24.5" from node_modules/react-email

What other react projects do you guys have in your monorepo?

zackperdue avatar Dec 20 '24 21:12 zackperdue

Same here using bun

coratgerl avatar Jan 05 '25 19:01 coratgerl

I ran into this issue in a Turborepo monorepo after upgrading an app to Next 15. Our package email still had a dependency of React 18 from when it was created via create-email. After explicitly upgrading the package to React 19 (along with resend to 4.0.1, the dev server started working again along with sending emails via Resend.

I have a feeling this error is somehow connected to mismatched React versions between what @react-email exports and the React version of the client being used to import it.

lukeshumard avatar Jan 15 '25 17:01 lukeshumard

I had this problem in my turborepo monorepo. Basically just make sure react dependencies (react and react-dom) are hoisted at root node_modules

lcnlvrz-revelacion avatar Jan 17 '25 23:01 lcnlvrz-revelacion

I ran into this issue in a Turborepo monorepo after upgrading an app to Next 15. Our package email still had a dependency of React 18 from when it was created via create-email. After explicitly upgrading the package to React 19 (along with resend to 4.0.1, the dev server started working again along with sending emails via Resend.

I have a feeling this error is somehow connected to mismatched React versions between what @react-email exports and the React version of the client being used to import it.

This fixed it for me. Make sure your the react and react-dom versions are the same in your react-email package.json and your main code package.json.

Robd07 avatar Feb 15 '25 04:02 Robd07

Ok, I wasted like 2 hours on this and was absolutely baffled at wtf was going on because I don't use React but I use this to generate emails on my backend. After a long rabbit hole here is what was happening. I didn't have react 18.3.1 in my peerDependencies so it was installing React 19 instead. As soon as I specified the peerDependencies and removed the React 19 package from node_modules and did a new npm i it fixed the issue. I still don't 100% understand why it chose React 19 but I am assuming it has something do with how npm resolves dependencies.

elucidsoft avatar Mar 04 '25 12:03 elucidsoft

in my case I was using renderAsync, I believe it is just the matter how we can carefully match between "react/dom version" with "react-email/components version", previously my react/dom version is 18.3.1, turns out, my react-email/components 0.0.11 is too old

I checked it:

npm view @react-email/[email protected]  peerDependencies
{ react: '18.2.0' }

So I tried to use newer version with react v19:

❯ npm view @react-email/[email protected] peerDependencies
{ react: '^18.0 || ^19.0 || ^19.0.0-rc' }

when I see the newer version,

import { renderAsync } from "@react-email/components";

const getData = async (component: JSX.Element) => {
  console.log(component)
  const html = await renderAsync(component);
  return html;
};

"@react-email/components": "^0.0.33", "react": "19.0.0", "react-dom": "19.0.0",

works beautifully. but yeah it has a lot of things to do, when it comes to increase the react version, there are some peer dependencies that needs to be resolved first, take a while for me to get rid of them. Image

gnomefin avatar Mar 05 '25 07:03 gnomefin

I was running into this error when running the dev server. I had specified [email protected] in my package.json but did not specify a react-dom package. So when I upgraded to [email protected], it had a mismatched dependency with @react-email/render.

Adding [email protected] to my package.json resolved the issue.

JonBernardHealia avatar Mar 06 '25 20:03 JonBernardHealia

I wasted a hour trying to use newer versions of packages. did not work. I am not sure they test any of the stuff. Just stick to something that work.

"react": "npm:[email protected]", "resend": "npm:[email protected]", "@react-email/components": "npm:@react-email/[email protected]"

jt6677 avatar Mar 21 '25 10:03 jt6677

Anyone found a fix for this ?

ayoubbouyaara avatar Mar 21 '25 22:03 ayoubbouyaara

ALRIGHT GUYSSSS SO I FOUND SOMETHING. I'm using resend/react-email on honojs with typescript. In tsconfig, I changed my "jsx" field to "react" instead of react-jsx. All I need to do now is import react like this "import React from 'react';" in every file/component where react components are used. tsconfig.json should look like this:

{ "compilerOptions": { "strict": true, "jsx": "react", "allowSyntheticDefaultImports": true, "esModuleInterop": true, "target": "ESNext", "module": "NodeNext", "moduleResolution": "nodenext", "resolveJsonModule": true, "allowImportingTsExtensions": true, "noEmit": true, } }

And add this at the top of every file where components are used/declared:

import React from 'react';

I didn't really dig into the "how and why", but it did fix it for me. Hope this will help someone !

ayoubbouyaara avatar Mar 23 '25 22:03 ayoubbouyaara

ALRIGHT GUYSSSS SO I FOUND SOMETHING. I'm using resend/react-email on honojs with typescript. In tsconfig, I changed my "jsx" field to "react" instead of react-jsx. All I need to do now is import react like this "import React from 'react';" in every file/component where react components are used. tsconfig.json should look like this:

{ "compilerOptions": { "strict": true, "jsx": "react", "allowSyntheticDefaultImports": true, "esModuleInterop": true, "target": "ESNext", "module": "NodeNext", "moduleResolution": "nodenext", "resolveJsonModule": true, "allowImportingTsExtensions": true, "noEmit": true, } }

And add this at the top of every file where components are used/declared:

import React from 'react';

I didn't really dig into the "how and why", but it did fix it for me. Hope this will help someone !

My setup is also honojs with bun and resend, and I'm porting loads of code from nextjs there everything was working, the jsx tsconfig has a value of "preserve" in my nextjs tsconfig . Changing the tsconfig like this solved my problem in my hono project

dariolourenco avatar Mar 27 '25 00:03 dariolourenco

Upgrading to React 19 fixed my issue:

"@react-email/components": "^0.0.36",
"react": "^19.1.0",
"resend": "^4.2.0",

dherault avatar Apr 14 '25 18:04 dherault