kiliman / remix-typedjson

This package is a replacement for superjson to use in your Remix app. It handles a subset of types that `superjson` supports, but is faster and smaller.
MIT License
435 stars 19 forks source link

`useTypedLoaderData` Syntax Error during another Error #16

Closed jmaldon1 closed 1 year ago

jmaldon1 commented 1 year ago

Trying to reproduce with a minimal example:

export const loader = async ({
    request,
  }: LoaderArgs): Promise<TypedJsonResponse<LoaderData>> => {

    const data = {
      date: new Date()
    };

    return typedjson(data);
};

export default function Dashboard(): JSX.Element {
  const loaderData = useTypedLoaderData<typeof loader>();
  const { date } = loaderData;
  // Purposefully throw an error to show issue
  throw new Error("TEST")
  return null;
}

Error message in browser console window:

Uncaught SyntaxError: Unexpected identifier 'Nov'

When checking the file its talking about in Sources tab You'll see something like this in the file:

'routeData':{'routes/dashboard': {'date':Thu Nov 03 2022 11:04:25 GMT-0400 (Eastern Daylight Time)}}'

Seems like the typedjson is causing syntax errors somewhere due to the type conversions?

jmaldon1 commented 1 year ago

If i replace useTypedLoaderData with useLoaderData this syntax error goes way and I get the actual TEST error message.

kiliman commented 1 year ago

I'm not seeing the error.

Note: I import Remix from a single file (which also overrides json and useLoaderData with my typed versions). See https://github.com/kiliman/rmx-cli#-gen-remix

Anyway, I'm getting the ErrorBoundary as expected.

A couple of things I noticed is that 1) You're explicitly typing your loader function return instead of letting TypeScript infer. 2) You use type LoaderData, but I don't see it in your example.

Can you provide a repo that shows the error you're having?

import { json, useLoaderData, type LoaderArgs } from '~/remix'

export async function loader({ request }: LoaderArgs) {
  return json({ date: new Date() })
}

export default function Index() {
  const loaderData = useLoaderData<typeof loader>()
  throw new Error('oops!')
  return (
    <>
      <h1>Data</h1>
      <pre>{JSON.stringify(loaderData, null, 2)}</pre>
    </>
  )
}

export function ErrorBoundary({ error }: { error: Error }) {
  return (
    <>
      <h1>Error</h1>
      <pre>{error.message}</pre>
    </>
  )
}
image image
jmaldon1 commented 1 year ago

So weird, its bugged on my repo (its a private repo, so its hard to share it) and I'm having trouble recreating on a fresh repo. I was able to narrow it down to the <Scripts /> tag in the root.tsx file. But i'm still trying to recreate it.

If I comment out the <Scripts />, it works normally.

This is what my root.tsx looks like (copied from https://github.com/mui/material-ui/tree/master/examples/remix-with-typescript):

import React, { useMemo } from "react";
import {
  Links,
  LiveReload,
  Meta,
  Outlet,
  Scripts,
  ScrollRestoration,
  useCatch,
  Link,
  useTransition,
  useFetchers,
} from "@remix-run/react";
import type { LinksFunction } from "@remix-run/node";
import { withEmotionCache } from "@emotion/react";
import { unstable_useEnhancedEffect as useEnhancedEffect } from "@mui/material";
import Button from "@mui/material/Button";
import Typography from "@mui/material/Typography";

const Document = withEmotionCache(
  ({ children, title }: DocumentProps, emotionCache) => {
    const clientStyleData = React.useContext(ClientStyleContext);

    // Only executed on client
    useEnhancedEffect(() => {
      // re-link sheet container
      // eslint-disable-next-line no-param-reassign
      emotionCache.sheet.container = document.head;
      // re-inject tags
      const { tags } = emotionCache.sheet;
      emotionCache.sheet.flush();
      tags.forEach((tag) => {
        // eslint-disable-next-line no-underscore-dangle
        (emotionCache.sheet as any)._insertTag(tag);
      });
      // reset cache to reapply global styles
      clientStyleData.reset();
      // eslint-disable-next-line react-hooks/exhaustive-deps
    }, []);

    return (
      <html lang="en">
        <head>
          <meta charSet="utf-8" />
          <meta name="viewport" content="width=device-width,initial-scale=1" />
          <meta name="theme-color" content={theme.palette.primary.main} />
          {title ? <title>{title}</title> : null}
          <Meta />
          <Links />
          <link
            rel="stylesheet"
            href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap"
          />
          <meta
            name="emotion-insertion-point"
            content="emotion-insertion-point"
          />
        </head>
        <body>
          {children}
          <ScrollRestoration />
          <Scripts />
          <LiveReload />
        </body>
      </html>
    );
  }
);

// https://remix.run/docs/en/v1/api/conventions#errorboundary
export function ErrorBoundary({ error }: { error: Error }): JSX.Element {
  console.error(error);

  return (
    <Document title="Error!">
      <Layout>
        <div>
          <h1>There was an error</h1>
          <p>{error.message}</p>
          <hr />
          <Typography align="center">
            <Button variant="outlined" component={Link} to="/">
              Bring me back to the site
            </Button>
          </Typography>
        </div>
      </Layout>
    </Document>
  );
}
jmaldon1 commented 1 year ago

@kiliman https://github.com/jmaldon1/typedjson-error-repro

Here we go, I basically deleted everything from my repo and tried to get to the minimum code with the error.

kiliman commented 1 year ago

Interesting. When Remix SSRs the route, it includes the loader data in the script along with the HTML, so during hydration it can re-render on the client. However, it's odd that it's embedding the converted loader data (from useTypedLoaderData) instead of just the data from the loader itself. Which in this case looks like, and would not cause the error.

{"date":"2022-11-03T20:57:56.527Z","__meta__":{"date":"date"}}

This only appears to happen during SSR and not when Remix fetches client side loaders.

The only big difference between your code and my repro is that you're using Emotion. Not sure if that's the culprit or not.

jmaldon1 commented 1 year ago

I can't seem to repro this when I clone this https://github.com/mui/material-ui/tree/master/examples/remix-with-typescript and try to recreate it, which uses emotion and MUI also.

jmaldon1 commented 1 year ago

Updated to "@remix-run/dev": "^1.7.5", and i think it fixed it lol