Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Loader data not type safe when returning an interface #2987

Open
go3323 opened this issue Dec 12, 2024 · 1 comment
Open

Loader data not type safe when returning an interface #2987

go3323 opened this issue Dec 12, 2024 · 1 comment

Comments

@go3323
Copy link

go3323 commented Dec 12, 2024

Which project does this relate to?

Router

Describe the bug

Route.useLoaderData does not retain the type safety of loaded data when the data is a Promise<T>, where T is an interface. The actual returned type is the shape of the object without the interface.

Your Example Website or App

https://stackblitz.com/edit/tanstack-router-at6s9t9e?file=src%2Froutes%2Fabout.tsx

Steps to Reproduce the Bug or Issue

  1. Open stackblitz
  2. Go to routes/about.tsx
  3. In AboutComponent(), observe that the type of loaderData is { id: number; name: string; email: string; } instead of UserData

Expected behavior

I expected Route.useLoaderData to return an object typed with its original interface from the data loader function. Instead, it returned the object shape without the interface.

Screenshots or Videos

No response

Platform

  • OS: Stackblitz
  • Browser: Chrome

Additional context

This regression was introduced in v1.81.0. Previous versions worked correctly and returned the interface instead of the object shape.

@jclem
Copy link

jclem commented Dec 14, 2024

I believe this comes from the key iteration in the Expand utility type, which iterates the keys of the loader data.

In the example provided by @go3323, it's not actually a problem, because the UserData interface is reproduced in full, and the provided example is actually fully type safe.

However, consider an example (this is only valid with non-server-side routing, of course) where a loader returns a value that has private or protected fields, which are not iterated over by keyof (Stackblitz):

import { createFileRoute } from '@tanstack/react-router';

class UserData {
  id: number;
  name: string;
  #email: string;

  constructor(id: number, name: string, email: string) {
    this.id = id;
    this.name = name;
    this.#email = email;
  }

  getEmail() {
    return this.#email;
  }
}

export const Route = createFileRoute('/')({
  loader: () => Promise.resolve(new UserData(1, 'user', '[email protected]')),
  component: HomeComponent,
});

function HomeComponent() {
  const data = Route.useLoaderData();
  return <UserDataEmail data={data} />;
}

function UserDataEmail(props: { data: UserData }) {
  return props.data.getEmail();
}

In this example, you'll get a type error when using the UserDataEmail component:

Property '#email' is missing in type '{ id: number; name: string; getEmail: () => string; }' but required in type 'UserData'.

Note that as @EskiMojo14 helpfully pointed out to me on Discord, Expand is shallow, so you can currently work around this by returning the data in question wrapped in a plain object:

export const Route = createFileRoute('/')({
  loader: () => Promise.resolve({ user: new UserData(1, 'user', '[email protected]') }),
  component: HomeComponent,
});

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants