remix: Incorrect type from `useLoaderData`
What version of Remix are you using?
1.6.7
Steps to Reproduce
export const loader = async () => {
return {
requestedDomains: [
{ domain: "example.com", status: "pending" },
{ domain: "saulgoodman.org", status: "approved" },
{ domain: "example.org", status: "rejected", reason: "Not allowed" },
],
};
};
export default function Foo() {
const { requestedDomains } = useLoaderData<typeof loader>();
...
}
Expected Behavior
the type of requestedDomains should be
SerializeObject<{
domain: string;
status: string;
reason?: string;
}>[]
Actual Behavior
the type of requestedDomains is:
(SerializeObject<{
domain: string;
status: string;
reason?: undefined;
}> | SerializeObject<{
domain: string;
status: string;
reason: string;
}>)[]
See also this discussion where I try some solutions.
About this issue
- Original URL
- State: closed
- Created 2 years ago
- Reactions: 19
- Comments: 44 (8 by maintainers)
For the remix tutorial, adding ‘unknown’ converstion seemed to make TS happy
Unfortunately I think we’re going to continue to get a lot of these weird edge cases. The issue is that Remix is trying to convert your actual data type, with all its various forms, and convert that to what the type would look like if you replaced any non-JSON types with their serialized versions.
They are using a home-grown function to do this, but plan to use
Jsonifyfromtype-fest. Even then, I’m not sure it it will every be 100% perfect.I side-stepped this with
remix-typedjsonby simply using whatever type that TypeScript thinks the loader is returning. In order to do this, I also wrote my own JSON serialize/deserialize function (similar tosuperjson) to ensure that non-JSON values likeDate,BigInt, etc. are converted back to their native values after parsing.Can this be included in remix, types returned from remix are unusable. When using prisma, Remix Json:
SerializeObject<UndefinedToOptional<Person>>[]Typed Json:Person[]@kiliman Thanks!
Yeah, I’m not a big fan of the
SerializeObjecttype. That’s why I wroteremix-typedjson. It will automatically convert non-JSON types back into their native types, so you can use the actual types instead of the JSON-converted ones.Don’t want to sound rude, but a feedback to the Remix Team - Facing the same issue and this has been frustrating! I had heard a lot about Remix but these kinds of errors while trying out its first tutorial makes me not want to use it for projects 😦
@sslgeorge the
loaderfunction can return only JSON values, remix needs to correctly convert non-JSON types in JSON compatible types, let me show you a simple example:This code is wrong, because the method
getFullYear()does not exists on theStringobject.But isn’t
foosupposed to be aDateobject? Yes, but you can’t send objects likeDate,BigIntor classes over the wire, you need first to convert them in a format that can go over the wire. This format is JSON. The process to convert non json values to json values in this scenario is called network serialization.This is why the
SerializedObjecttype is required, is converts non-JSON types to JSON types.For now
typedJsonseems to be the best alternative. But I’m still not sold on the idea it’s not handled by Remix itself.I think the solution here is using
SerializeFromfrom@remix-run/server-runtime.e.g.
For other people’s reference, this is force casting: https://www.w3schools.com/typescript/typescript_casting.php
What would be the logical thing to do if done correctly without force casting? Should
LoaderDatatype be updated oruseLoaderData()method be updated. Ideally useLoaderData() should not have to go throughunknowncasting, so curious.Another example:
bis never there.Changing the type of
ain thex === 3case fixes the typeThe conclusion I was able to draw thus far is if an object is returned that has a property of the same type, but an extra property added as well, it doesn’t get picked up.
If I take the first example and completely remove the
x === 2case, thebproperty is picked up and the type if as follows:I confirm I have the same “bug” of TS types with the tutorial
🤖 Hello there,
We just published version
2.1.0-pre.0which involves this issue. If you’d like to take it for a test run please try it out and let us know what you think!Thanks!
Nice! 💪
That still requires a manual step to register, which if you are using 3rd party types might be onerous. I think its a good tradeoff and the right design choice for
remix-typedjson, but just mentioning it for others who might want some further nuance on tradeoffs.So a lot has passed since I opened this issue and I read all your comments.
The snippet I shared at the start was a simplified version of a
loaderI actually have in a project for Shopify, in this time I enhanced the project, updating also remix to1.14.1, so I would like to share with you some of my thoughts.For this, take the following
loaderfor an embedded Shopify app:The return type of this
loaderisSo you can’t correctly discriminate this union:
This raise an error since
loaderDatais of type:So, should we use a
LoaderDatatype? Well, you could:but from my little experience I don’t see this approach scalable, since if the
loaderhas to return another object, you should also update theLoaderDatatype, otherwise you will get some errors.I don’t like this approach, so instead I started to use
as conston returns:From this typescript infer the following type:
That is a truly discriminated union, and now
loaderDataisThis works great, no force casting with
unknownand noLoaderDatatype.A little enhancement from Remix would be to remove from the type keys with
undefinedas value, since they won’t exists in the returned json, but I think it is ok now.@miniplus this could also fix your issue, however I don’t know why you never see
b, I use typescript 4.9.5, and the following typescript works:The type of
baris:Here is a playground
@uhrohraggy from the type you posted,
usershould beUser | null, and notUser | undefined,nullwill be correctly serialized into the json,undefinedwon’tIs there any update on whether a solution akin to
remix-typedjsonwill be introduced within Remix?+1 this is definitely confusing and not clear how best to handle. I would fully expect to be able to do this:
given a
loaderfunction akin to,quick edit: thinking about this some more, perhaps a
prisma-clientmodel could automatically provide a toJSON() serializer …perhaps it’s notjson’s job to handle this, but having it in the indie-stack example makes it confusing when slight adjustments leads to theSerializeObject<UndefinedToOptional<Note>>[]issue.