remix: useLoaderData type inference broken when returned data has a `data` key

What version of Remix are you using?

1.11.1

Steps to Reproduce

If the object returned from the loader includes a data key, the type inference returns only the items nested under that key. So in this case, typescript thinks useLoaderData returns just {item: number}. The code works just fine - item and other are being destructured properly and displaying on the page, it’s just the types that are wrong. If I rename the data key to something else, it works fine and typescript returns the full type, e.g. {myData: {item: number}, other: number}. This started when defer was released.

export const loader = async () => {
  return json({
    data: { item: 1 },
    other: 2
  })
}

export default function Index() {
  const { data: { item }, other } = useLoaderData<typeof loader>();

  return <div>Item: {item}, Other: {other}</div>;
}
> yarn tsc
yarn run v1.22.19
$ /Users/dylan/dev/data-issue/node_modules/.bin/tsc
app/routes/index.tsx:12:11 - error TS2339: Property 'data' does not exist on type 'SerializeDeferred<{ item: number; }>'.

12   const { data: { item }, other } = useLoaderData<typeof loader>();
             ~~~~

app/routes/index.tsx:12:27 - error TS2339: Property 'other' does not exist on type 'SerializeDeferred<{ item: number; }>'.

12   const { data: { item }, other } = useLoaderData<typeof loader>();
                             ~~~~~

Expected Behavior

Type inference should work as it did before, or if data is a reserved name, the documentation should say as much.

Actual Behavior

Typescript infers the wrong types, but the code works fine.

About this issue

  • Original URL
  • State: closed
  • Created a year ago
  • Reactions: 28
  • Comments: 18 (8 by maintainers)

Most upvoted comments

It looks like it resolved the issue for us from what I can see.

Closed by #5516

Looks like this is the problem. https://github.com/remix-run/remix/blob/006b58bfec02622f9881c1e37444baef60584eda/packages/remix-server-runtime/serialize.ts#L21-L31

https://github.com/remix-run/remix/blob/006b58bfec02622f9881c1e37444baef60584eda/packages/remix-server-runtime/responses.ts#L9-L14

TypedDeferredData<infer U> matches on our result because there is a data key, instead of moving down to SerializeObject which it should since I’m not actually using defer.

1.16.0 is out 🎉

This is still an issue with actions, typed loaders are working for me.

Meaning, useActionData<typeof action>() is inferred completely wrong.

I’ve this problem as well with the latest version of Remix. I just wanted to add that it also happens when using useFetcher in actions.

Reproduced this in a minimal repository for you: https://github.com/emilbryggare/remix-types/

import type { LoaderArgs } from "@remix-run/node";
import { json } from "@remix-run/node";
import { useLoaderData } from "@remix-run/react";

export const loader = async ({ request }: LoaderArgs) => {
  // Generate correct types.
  // return json({ myData: { message: "Hello World!" } });

  // Does not generate correct types, e.g. SerializeDeferred
  return json({ data: { message: "Hello World!" } });
};
export default function Index() {
  let loaderData = useLoaderData<typeof loader>();
  // Type is when data key is present in return object:
  //   SerializeDeferred<{
  //     message: string;
  // }>
  return (
    <div style={{ fontFamily: "system-ui, sans-serif", lineHeight: "1.4" }}>
      <h1>Welcome to Remix</h1>
    </div>
  );
}

This still occurs on Remix 1.15, and it also occurs on the SerializeFrom type helper, and the useFetcher<Type>() generic type.

I also have a typescript playground for testing.