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

`useMatches` type inference #18

Closed lukebowerman closed 1 year ago

lukebowerman commented 1 year ago

I found myself wanting to access a loader response generated by remix-typedjson via useMatches and ran into difficulty trying to specify the type of the data attribute.

I tried doing various incantations of:

match as Awaited<Promise<ReturnType<typeof loader>>>

In so doing I was never able to get the typedJson unwrapped from the response type, the closest I managed to get was:

Response & { typedjson(): Promise<{ foo: string, bar: number }> }

When Remix 1.10 lands useRouteLoaderData (https://reactrouter.com/en/main/hooks/use-route-loader-data) seems like it'll offer a better model for accessing loader data from elsewhere in the tree much more elegantly than the useMatches route so perhaps I'm barking up the wrong tree entirely.

kiliman commented 1 year ago

For now, I think the simplest way is to do:

import { loader as rootLoader } from '~/root'

type RootLoaderData = typeof rootLoader
const matches = useMatches()
const data = matches[0].data as RootLoaderData

And change which loader type you're trying to access. But yeah, useRouteLoaderData will let you specify a generic loader type, so DX is better.

lukebowerman commented 1 year ago

So what I was doing was fairly similar - but because the loader returns a json from remix-typedjson I've struggled to get the typeof loader to behave.

I think what I need to emulate is what's happening in the useTypedLoaderData (not the actual JSON parsing but the type-assignment bits) but basically trying to "tear off" the TypedJsonResponse type wrapped around the result similar to what the Awaited type allows one to do for promises.

I put together a small example repo at: https://github.com/lukebowerman/remix-example/blob/main/app/routes/index.tsx#L7-L17 to try to show what I'm bumping into.

marcojakob commented 1 year ago

@lukebowerman The TypeScript magic of UseDataFunctionReturn seems to do the trick if you want to unwrap TypedJsonResponse.

type UseDataFunctionReturn<T extends DataOrFunction> = T extends (
  ...args: any[]
) => infer Output
  ? Awaited<Output> extends TypedJsonResponse<infer U>
    ? U
    : Awaited<ReturnType<T>>
  : Awaited<T>

type LoaderType = UseDataFunctionReturn<typeof loader>;
lukebowerman commented 1 year ago

Thank you @marcojakob - that was exactly what I was looking for :)