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

Invalid Hook Call

Open mckelveygreg opened this issue 1 year ago • 5 comments

  • Component or Package Name: jsxToString
  • Component or Package Version: 1.4.1
  • @jsxp-email/cli Version?: 3.0.1
  • Operating System (or Browser): OSX
  • Node Version: 20.6.1
  • Link to reproduction (⚠️ read below): https://stackblitz.com/edit/jsx-email-repro-9a6jzc?file=templates%2FReproduction.tsx

Expected Behavior

To be able to use 3rd party libs that may have react hooks in them (ie useMemo for performance concerns in a browser)

The library that alerted me to this is, @portabletext/react and their component PortableText. This enables us to render rich text from our CMS into emails. This code was able to run in the original react-email, but I'm unsure of how the library has shifted in this respect since then.

Actual Behavior

Throws Invalid Hook Call errors during the jsxToString function call

Additional Information

stack trace

chunk-I7UPNA6J.js?v=83f9a7a2:3509 Uncaught TypeError: Cannot read properties of null (reading 'useMemo')
    at useMemo (chunk-I7UPNA6J.js?v=83f9a7a2:3509:29)
    at ThirdPartyUseMemo (Reproduction.tsx?t=1703115101276:2146:16)
    at jsxToString (jsx-email.js?v=bf6a5f6d:24773:26)
    at jsxToString (jsx-email.js?v=bf6a5f6d:24766:24)
    at jsxToString (jsx-email.js?v=bf6a5f6d:24773:14)
    at jsxToString (jsx-email.js?v=bf6a5f6d:24725:22)
    at async jsxToString (jsx-email.js?v=bf6a5f6d:24766:18)
    at async render (jsx-email.js?v=bf6a5f6d:25206:15)
    at async main.tsx:67:16
    at async Promise.all (jsxemailrepro9a6jzc-exbx--55420--a2aabdd9.local-credentialless.webcontainer.io/index 0)
useMemo @ chunk-I7UPNA6J.js?v=83f9a7a2:3509
ThirdPartyUseMemo @ Reproduction.tsx?t=1703115101276:2146
jsxToString @ jsx-email.js?v=bf6a5f6d:24773
jsxToString @ jsx-email.js?v=bf6a5f6d:24766
jsxToString @ jsx-email.js?v=bf6a5f6d:24773
jsxToString @ jsx-email.js?v=bf6a5f6d:24725
Show 5 more frames
Show less

mckelveygreg avatar Dec 20 '23 23:12 mckelveygreg

Generally speaking, hooks aren't supported (right now). That's because email templates aren't interactive, and hooks are only really useful in interactive/reactive components that require hydration. While you might have an interactive/reactive component re-use use-case, they're really not a good fit for email templates because at the end of the day, email template outputs are static.

Immediate options for getting you past this:

  • use props with values coming from the parent component, or the component or code which is calling render (even if that's the CLI - you can use --props)
  • pass children in as props. (if not using the CLI)

I'll take a look at what it would take to support hooks, but it's important to understand what jsx-email (and even react-email and mjml) are actually providing to you - it's not a react app, it's a static file template engine with sugar to make the task easy.

shellscape avatar Dec 21 '23 01:12 shellscape

I understand this isn't a React app, and I don't expect state or anything, but this is a third party lib that turns rich text into DOM.

Looks like I should try to find an alternative method from that lib.

mckelveygreg avatar Dec 21 '23 01:12 mckelveygreg

This particular error seems to be common, with a common cause. Going to investigate a little bit on my end to see if our bundle may be to blame.

shellscape avatar Dec 21 '23 02:12 shellscape

So investigating this actually surfaced a few issues that will result in improvements, but none that are directly related to this issue unfortunately. There were dual versions of react within the bundled template (which is output to a temp directory); one in the bundled template, and one in node_modules for the project itself. That's resolved. The bundling of the email templates are actually faster now since it's excluding jsx-email and react. That took the bundled file size for a template from 7mb to 25kb. So that's fun. But again, won't resolve this issue.

This is probably related to react's internal dispatcher but not quite sure yet what the deal is there.

shellscape avatar Dec 21 '23 03:12 shellscape

Whelp, found a work around for this! If I just render the react to html myself, then everyone is happy 🤷

import { PortableText } from "@portabletext/react"
import { renderToString } from "react-dom/server"

export const Template = () => {
  const html = renderToString(
    <PortableText
      value={body.filter(isTypedObject)}
      components={dynamicComponents}
      onMissingComponent={onMissingComponent}
    />
  )
return (
  <div dangerouslySetInnerHTML={{
                __html: html,
              }}
           />
 )
}

So I guess we could close? 🤷

mckelveygreg avatar Jan 06 '24 00:01 mckelveygreg

Glad you found a way around that. We have some improvements coming in v2.0.0 which has a PR up and will be released soon (https://github.com/shellscape/jsx-email/issues/203). With that I'll close this one. Thanks again for reporting.

shellscape avatar Sep 14 '24 21:09 shellscape