Replies: 10 comments
-
Now that the source is released I did a bit of messing around and think I found an ideal way to implement by modifying some of remix's own types. from responses.ts: export interface ResponseWithBody<Body> extends Response {}
/**
* A JSON response. Converts `data` to JSON and sets the `Content-Type` header.
*/
export function json<Body>(
data: Body,
init: number | ResponseInit = {}
): ResponseWithBody<Body> { /* snip */ }```
and from routeModules.ts:
```ts
/**
* The argument that is passed to a LoaderFunction
*/
export type LoaderFunctionArg = {
request: Request;
context: AppLoadContext;
params: Params;
};
/**
* A function that loads data for a route.
*/
export interface LoaderFunction {
(args: LoaderFunctionArg):
| Promise<Response>
| Response
| Promise<AppData>
| AppData;
}
/**
* Used to infer the JSON body of a LoaderFunction
*/
export type InferLoaderData<
Loader extends (arg: LoaderFunctionArg) => any
> = Loader extends (arg: LoaderFunctionArg) => Promise<infer Resp>
? InferLoaderData<(arg: LoaderFunctionArg) => Resp>
: Loader extends (arg: LoaderFunctionArg) => ResponseWithBody<infer Body>
? Body
: Loader extends (arg: LoaderFunctionArg) => Response
? never
: ReturnType<Loader>; With that, loader types can be inferred like the following: export async function loader() {
return {
hello: 'world'
}
}
export default function Page() {
// routeData is inferred as type { hello: string }
let routeData = useRouteData<InferLoaderData<typeof loader>>()
return null |
Beta Was this translation helpful? Give feedback.
-
Alternative suggestion: interface AppData {
hello: string;
}
// new type parameter for `MetaFunction`
export let meta: MetaFunction<AppData> = () => {}
// new type parameter for `LoaderFunction`
export let loader: LoaderFunction<AppData> = () => {}
export default function Page() {
// already works
let data = useRouteData<AppData>();
return null;
} Because it's not always clear if the And it's just straight up easier to grok and removes some of the burden from the TypeScript compiler. Because the compiler might eventually break down with inference and give you an |
Beta Was this translation helpful? Give feedback.
-
Declaring the type explicitly is all well and good but I think this issue is about how to avoid doing that. That's what it means to infer a type (https://www.typescriptlang.org/docs/handbook/type-inference.html). Depending on inference has drawbacks but also has benefits, and it's a choice I'd like Remix to offer |
Beta Was this translation helpful? Give feedback.
-
With my proposal the inference from the loader function is trivial: import type { LoaderFunction as RemixLoaderFunction } from 'remix';
type AppData<LoaderFunction> = LoaderFunction extends RemixLoaderFunction<infer AppData> ? AppData : never |
Beta Was this translation helpful? Give feedback.
-
The code would be more complicated than you make it out to be because many |
Beta Was this translation helpful? Give feedback.
-
That is my suggestion of course. To add a generic argument. Let's be a bit more generous with each positions, shall we? I expected that it's obvious that my proposal isn't possible today. That's why I proposed it. Otherwise there wouldn't be anything to argue about.
How so? What would work with |
Beta Was this translation helpful? Give feedback.
-
You're right that we're basically agreed about what we'd like to see added to Remix and are arguing about implementation details. And that's probably a silly thing to do without some signal of intent from Ryan/Michael. |
Beta Was this translation helpful? Give feedback.
-
I got something working. I made the following changes.
import { DataFunctionArgs } from '@remix-run/server-runtime'
export const loader = async ({ params }: DataFunctionArgs) => {
invariant(params.movieId, 'expected params.movieId')
const details = await new Tmdb().movie.getDetails(params.movieId)
return { details }
}
export function useInferredRouteData<
T extends (args: DataFunctionArgs) => any
>() {
return useLoaderData<Awaited<ReturnType<T>>>()
}
export default function Index() {
const { details } = useInferredRouteData<typeof loader>() Warning: I realize that this is incomplete. If you have conditional response types on the loader function you may run into errors with this code. But it's great for a happy path and the Type Name Suggestion: I'd also suggest to the remix team that |
Beta Was this translation helpful? Give feedback.
-
I thought the same too at first, but I figure it's called that because the same args are also used for the |
Beta Was this translation helpful? Give feedback.
-
Related to the subject, here are a set of typed helpers I've written up that I'm making good use of: https://gist.github.com/itsMapleLeaf/0521a21e5e24419ce90d447e7ceede70 |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
It would be nice to infer the type of the loader function when using
useRouteData
.I'm not sure how the internals of remix work, so I don't know if there's anywhere one could hook (pun intended) into the place where the actual loading occurs.
Anyway, here is a small utility that could help someone else needing an easy way to get the types.
This code (placed in a helper file somewhere):
allows the user to get the types in one of two ways:
1 - Add the type to the existing hook
let data = useRouteData<LoaderType<typeof loader>>()
2 - Use the provided wrapper
let data = useTypedRouteData(loader)
This should work equally well with promises and responses. But there's probably a lot of edge cases I'm not thinking of right now.
Beta Was this translation helpful? Give feedback.
All reactions