remix icon indicating copy to clipboard operation
remix copied to clipboard

Type mismatch when returning from useLoaderData

Open ananevam opened this issue 1 year ago • 9 comments

Reproduction

When strictNullChecks is disabled, if all fields within a field are optional, then the parent field itself becomes optional. This is due to wrapping the returned result in a JsonifyObject. Sorry for my bad English.

type Result = {
    foo: {bar?: string}
};

export const loader = () => {
    const foo: Result = {foo: {bar: '123'}}
    return json(foo)
}

export default function Component() {
    const data = useLoaderData<typeof loader>()
    const foo: Result = data;
}
Type 'JsonifyObject<Result>' is not assignable to type 'Result'.
  Property 'foo' is optional in type 'JsonifyObject<Result>' but required in type 'Result'.

playground

System Info

-

Used Package Manager

npm

Expected Behavior

types are the same

Actual Behavior

types don't match

ananevam avatar Feb 24 '24 09:02 ananevam

Experiencing the same issues.

iampeter avatar Feb 27 '24 07:02 iampeter

i generally have to do return json<Result>(...) to get types to work right

littlejustinh avatar Feb 27 '24 13:02 littlejustinh

It's one of the reasons I created remix-typedjson. It exposes the native TypeScript types and converts the JSON data back to native types automatically.

https://github.com/kiliman/remix-typedjson

kiliman avatar Feb 27 '24 14:02 kiliman

@kiliman great that you've created the repo, I'll check it out.

But I think this is a core Remix issue, isn't it?

iampeter avatar Feb 28 '24 00:02 iampeter

@iampeter I think any framework that serializes to JSON from server to client will have this issue. I believe once Remix supports RSC, they'll be able to stream native types to the client, and this will no longer be an issue.

kiliman avatar Feb 28 '24 13:02 kiliman

@iampeter I think any framework that serializes to JSON from server to client will have this issue. I believe once Remix supports RSC, they'll be able to stream native types to the client, and this will no longer be an issue.

we are not talking about serialization of types, but about what type useLoaderData returns.

it works correctly

type ExtractGeneric<Type> = Type extends TypedResponse<infer X> ? X : never
export function useDataFromLoader<T extends LoaderFunction>() {
  return useLoaderData() as ExtractGeneric<Awaited<ReturnType<T>>>
}

playground

ananevam avatar Feb 28 '24 13:02 ananevam

import { useLoaderData } from "@remix-run/react"

export const useCustomLoaderData = <T extends (...args: any) => any>() => {
  return useLoaderData() as Awaited<ReturnType<T>>
}

Piotrovskyi avatar Jul 15 '24 18:07 Piotrovskyi