gregermendle / remix-mailer

Preview and test emails in Remix using [BLANK]
https://remix-mailer.gregermendle.com
MIT License
20 stars 0 forks source link

Type errors in preview loader #1

Closed jam-fran closed 1 week ago

jam-fran commented 9 months ago

Hi there,

Thanks for creating this package - I love it.

I've set up my preview route just link the docs lay out, however I'm getting type errors on the object with component/route definitions and the Compent.PreviewProps in the render function. Here's my route:

import { renderAsync } from '@react-email/render'
import type { LinksFunction, LoaderFunctionArgs } from '@remix-run/node'
import { json } from '@remix-run/node'
import { createPreviews } from 'remix-mailer/server/create-previews'
import { requireDev } from 'remix-mailer/server/require-dev'
import remixMailerStylesheet from 'remix-mailer/ui/index.css'
import { PreviewBrowser } from 'remix-mailer/ui/preview-browser'

import { Reminder } from '~/emails/Reminder'
import UserDeleted from '~/emails/UserDeleted'

export const links: LinksFunction = () => [
  {
    rel: 'stylesheet',
    href: remixMailerStylesheet,
  },
]

export const loader = async ({ request }: LoaderFunctionArgs) => {
  requireDev()

  const previews = await createPreviews(
    request,
    {
      reminder: Reminder, // TYPE ERROR HERE
      userDeleted: UserDeleted, // TYPE ERROR HERE
    },
    {
      render: (Component) =>
        renderAsync(<Component {...Component.PreviewProps} // TYPE ERROR HERE />),
    }
  )

  return json({
    ...previews,
  })
}

export default PreviewBrowse

Here's the type error for the component/route definitions:

Type '{ ({ vendorId, vendorName, vendorLogoUrl, daysNotice, milestone, }: Props): JSX.Element; PreviewProps: { vendorId: number; vendorName: string; vendorLogoUrl: string; daysNotice: number; milestone: "renewal"; }; }' is not assignable to type 'ComponentType<{}>'.
  Type '{ ({ vendorId, vendorName, vendorLogoUrl, daysNotice, milestone, }: Props): JSX.Element; PreviewProps: { vendorId: number; vendorName: string; vendorLogoUrl: string; daysNotice: number; milestone: "renewal"; }; }' is not assignable to type 'FunctionComponent<{}>'.
    Types of parameters '__0' and 'props' are incompatible.
      Type '{}' is missing the following properties from type 'Props': vendorId, vendorName, vendorLogoUrl, daysNotice, milestone ts(2322)

And here's the type error on Component.PreviewProps:

Property 'PreviewProps' does not exist on type 'ComponentType<{}>'.
  Property 'PreviewProps' does not exist on type 'ComponentClass<{}, any>'.ts(2339)

I've confirmed that have "moduleResolution: Bundler" in my tsconfig.js. Any ideas?

gregermendle commented 9 months ago

Hey, I'm glad you've been enjoying the project!

For your UserDeleted component, is that exported as a default or named export? I think that may be what its complaining about.

edit: just kidding i misread the error. let me dig in a bit.

jam-fran commented 9 months ago

Awesome thanks. And yeah, I've tried both default and named exports for each component.

In case it helps, here's one of the Email components that's being imported:

import { Column, Img, Link, Row, Section, Text } from '@react-email/components'

import { Constants } from '~/utils/constants'

import { AdminFooter } from './components/Footer'
import { Headline } from './components/Headline'
import { Layout } from './components/Layout'

type Props = {
  id: number
  fullName: string | null
  usageTimePeriod: string
  apps: {
    id: number
    name: string
    logoUrl: string | null
    lastAccessed: string
  }[]
}

const UserDeleted = ({ id, fullName, usageTimePeriod, apps }: Props) => {
  return (
    <Layout previewText="A user has been deactivated from your Hapstack account. Here's a report of the apps they were using.">
      <Section>
        <Headline>
          {fullName || 'An unnamed user'} was deactivated from your Hapstack
          account.
        </Headline>
        <Text>
          Here's a list of all the apps in your stack they've used over the past{' '}
          {usageTimePeriod}. You can view the full list on{' '}
          <Link href={`${Constants.webAppUrl}/team/${id}`}>
            their activity page
          </Link>{' '}
          in your Hapstack account.
        </Text>
      </Section>

      <Section className="mt-4 text-sm">
        <Row className="font-medium">
          <Column className="w-3/5">App</Column>
          <Column className="w-2/5">Last Accessed</Column>
        </Row>
        {apps.map((app) => (
          <Row
            key={app.id}
            className="border-b border-gray-200 py-3 last:border-b-0"
          >
            <Column className="inline-flex w-3/5 items-center">
              <div className="inline-flex leading-4">
                {app.logoUrl ? (
                  <Img
                    src={app.logoUrl}
                    alt={app.name}
                    className="mr-2 h-4 w-4 rounded-full"
                  />
                ) : null}
                <span className="w-32 truncate">{app.name}</span>
              </div>
            </Column>
            <Column className="w-2/5">{app.lastAccessed}</Column>
          </Row>
        ))}
      </Section>

      <AdminFooter />
    </Layout>
  )
}

UserDeleted.PreviewProps = {
  id: 1,
  fullName: 'Michael Scott',
  usageTimePeriod: '30 days',
  apps: [
    {
      id: 1,
      name: 'Slack',
      lastAccessed: '3 days ago',
      logoUrl: 'https://picsum.photos/200',
    },
    {
      id: 2,
      name: 'Notion',
      lastAccessed: 'about a week ago',
      logoUrl: 'https://picsum.photos/200',
    },
  ],
}

export { UserDeleted }
gregermendle commented 9 months ago

Thanks for sending over the component, was actually just about to ask. Do both of the components have a PreviewProps set? In my own setup if I remove it from one of the email templates it will complain similar to the way yours is complaining. I may be able to fix that in the remix-mailer type.

gregermendle commented 9 months ago

for example this is what i have for one of my templates locally, which is very very similar to yours.

import {
  Body,
  Head,
  Heading,
  Hr,
  Html,
  Link,
  Preview,
  Text,
} from "@react-email/components";

interface WelcomeProps {
  name?: string;
}

export const Welcome = ({ name }: WelcomeProps) => {
  const firstName = typeof name === "string" ? name.split(" ")[0] : "stranger";
  return (
    <Html>
      <Head />
      <Preview>Welcome to rvsn</Preview>
      <Body style={mainStyle}>
        <div style={headerStyle}>
          <div style={headerInnerStyle}>rvsn</div>
        </div>
        <div style={centeredStyle}>
          <Heading style={headingStyle}>Hiya {firstName}!</Heading>
          <Text style={paragraphStyle}>
            Welcome to rvsn! We're happy you're here and look forward to seeing
            you around. Feel free to reach out if you need anything ;)
          </Text>
        </div>
        <Hr style={hrStyle} />
        <div style={{ ...centeredStyle, padding: "12px 20px 0" }}>
          <Link href="https://rvsn.xyz" style={footerStyle}>
            rvsn
          </Link>
        </div>
      </Body>
    </Html>
  );
};

Welcome.PreviewProps = {
  name: "Jack Dangler",
};

export default Welcome;

const mainStyle = {
  margin: "0",
  backgroundColor: "#f6f6f4",
  maxWidth: "100%",
  fontFamily:
    '-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif',
};

const headerInnerStyle = {
  maxWidth: "400px",
  margin: "auto",
  fontFamily:
    "ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace",
};

const centeredStyle = {
  maxWidth: "400px",
  margin: "auto",
  padding: "0 20px",
};

const headerStyle = {
  backgroundColor: "#09090b",
  color: "#fff",
  fontSize: "14px",
  fontWeight: "600",
  padding: "14px 20px",
};

const headingStyle = {
  paddingTop: "20px",
  fontSize: "16px",
};

const paragraphStyle = {
  paddingTop: "6px",
};

const hrStyle = {
  borderColor: "#d8d8d5",
  margin: "80px 0 0",
};

const footerStyle = {
  fontSize: "12px",
  lineHeight: "1.5",
  color: "#ababab",
};
jam-fran commented 9 months ago

Yeah, if I remove PreviewProps from the component I still get a similar error:

Type '({ id, fullName, usageTimePeriod, apps }: Props) => JSX.Element' is not assignable to type 'ComponentType<{}>'.
  Type '({ id, fullName, usageTimePeriod, apps }: Props) => JSX.Element' is not assignable to type 'FunctionComponent<{}>'.
    Types of parameters '__0' and 'props' are incompatible.
      Type '{}' is missing the following properties from type 'Props': id, fullName, usageTimePeriod, apps ts(2322)
gregermendle commented 9 months ago

I'm having a bit of trouble reproducing on my end. Can you create a repo that reproduces the issue? Also, what version of react, remix, and typescript are you using?

jam-fran commented 9 months ago

Sure - here's a minimal reproduction: https://github.com/jam-fran/test-remix-mailer

I'm using the latest versions - remix 2.5.1, typescript 5.3.3, and react 18.2.0

gregermendle commented 9 months ago

Ok so I forked that and added some remix-mailer previews. I cant seem to get the same error on my end.

I will say that with that remix template, when I run npm run typecheck I was getting errors from vite which is imported as a optional peer dep from remix dev and certain modules imported by @react-email.

To fix that I added "skipLibCheck": true to the tsconfig.json and things cleared up. While that fixes the issue, it also prevents tsc from typechecking other .d.ts files which is not ideal. More on how it works here:

https://www.typescriptlang.org/tsconfig#skipLibCheck

FWIW Though, it looks like that was just enabled for the vite template in remix.

https://github.com/remix-run/remix/pull/8447

If you pull this repo down and install are you still seeing an issue?

https://github.com/gregermendle/test-remix-mailer.git

For me that works and doesn't give me any type errors. The only other thing I can think of at this moment is, if you are using vscode you can switch between which typescript version you are using. I am using the workspace version in my env. I've had that give me issues in the past with other projects when it is set to the VSCode version.

jam-fran commented 9 months ago

First of all - sorry for sharing a completely empty remix starter repo 🤦...must of forgotten a commit there

I don't get any type errors in your repo. I tried switching to the workspace version of typescript in my VS code with no luck, and have verified that skipLibCheck is true in my config. Totally stumped here!

The only other variable that's in play is that this is a package within a monorepo, but I can't imagine how that might be related to this issue given what I know.

Anyway, thanks so much for your help here. I'll reply back if I ever figure it out.

gregermendle commented 9 months ago

Hey! I didnt forget about this and went back today to test some things out and can now reproduce the issue you are seeing. So its a bit tricky and I'm going to try to get a type fix out for it soon. What seems to cause it on my end is if I have props in my Email components that are required.

interface WelcomeProps {
  name?: string;
}

becomes

interface WelcomeProps {
  name: string;
}

And causes a bunch of errors. For now the way I'm fixing it on my end is just making those props all optional. I do want to fix the errors though and will try to get something out soon.

Interestingly enough it looks like react-email in their preview system just casts those as React.FC and pulls the Preview Props out into their own variable: https://github.com/resend/react-email/blob/fb25c3d696e834ec9b1ac16b5b6102134418c923/packages/react-email/src/actions/render-email-by-slug.tsx#L35

Also sorry for not responding back to your previous comment, interviewing has basically sucked up all of my time lately.

jam-fran commented 9 months ago

Very interesting, thank for the update. Obviously this isn't urgent but I appreciate you continuing to look into it. Keep me posted!