remix: V2 Bug: New `JsonifyObject` type causing union type issues with Typescript
What version of Remix are you using?
2.0.1
Are all your remix dependencies & dev-dependencies using the same version?
- Yes
Steps to Reproduce
If you have a loader or action function which can return different object structures the new JsonifyObject abstraction type appears to not be properly understood by typescript meaning the code below cannot compile let alone run, even though it was valid in 1.19.* - and theoretically should be valid code.
app/routes/test.tsx
import type { ActionFunctionArgs } from "@remix-run/node";
import { useActionData } from "@remix-run/react";
import { json } from "@remix-run/node";
export async function action (args: ActionFunctionArgs) {
const formData = await args.request.formData();
const action = formData.get("action")?.toString() || "";
switch (action) {
case "a": return json({a: true});
case "b": return json({b: true});
default: throw new Response("Unexpected form action", {
statusText: "Bad request",
status: 400
});
}
};
export default function Index() {
const actionData = useActionData<typeof action>();
if (actionData?.a) console.log("It was a");
if (actionData?.b) console.log("It was B");
return <div>Hi</div>;
}
Expected Behavior
Typescript should correctly identify the type as effectively
const actionData: { a: boolean } | { b: boolean } | undefined;
And it indeeed identifying the type to be something close to this:
const actionData: JsonifyObject<{
a: boolean;
}> | JsonifyObject<{
b: boolean;
}> | undefined
Actual Behavior
Line 20: Property 'a' does not exist on type 'JsonifyObject<{ a: boolean; }> | JsonifyObject<{ b: boolean; }>'.
Property 'a' does not exist on type 'JsonifyObject<{ b: boolean; }>'.ts(2339)
Line 21: Property 'b' does not exist on type 'JsonifyObject<{ a: boolean; }> | JsonifyObject<{ b: boolean; }>'.
Property 'b' does not exist on type 'JsonifyObject<{ a: boolean; }>'.ts(2339
About this issue
- Original URL
- State: closed
- Created 9 months ago
- Reactions: 5
- Comments: 17 (4 by maintainers)
Also the update to have Jsonify take over as the type of all child objects of a Jsonify object is quite annoying. Because it then means any child object is defined as possibly being null, when actually it’s often not.
i.e. here


dictionaryRefis actually a1:minclude from prisma, so they’re not null….dictionary.clearly should not be nullI agree that it’s probably a Typescript weirdness issue, but this worked before 2.0, and then updating to 2.0 broke all of my type-checking, and this wasn’t even listed as a potential pain point like the changes to file routing, meta functions and the like.
I feel like this change really should have been outlined in the upgrading to v2
Reading #7246 it looks like there is a patch in the works which changes the type information to Typescript can infer the union type correct, am I understanding this correctly? If so I’ll live with the pain of
anyeverywhere until the patch is finally mainstream.esbuildunderstands TypeScript syntax, but does not do typechecking.BTW: I wrote
remix-typedjsonprecisely because of this issue. My package includes metadata about the types so youruserLoaderDatagets the actual TS type, and not theJsonifyObjectwrapper type.If I switch to TS 5.0.4 from 5.2.2, then issues go away… This is a TS issue then… 🫡 Thank you for your time
@alexanderMontague here’s a reproduction of issue without any Remix code. Its a consequence of how Typescript works and is not something fixable by Remix.
For more in depth explanation of TS behavior see: https://twitter.com/pcattori/status/1598359344827056131
This is how Typescript works with non-discriminated unions, and not a Remix bug.
If you want to narrow the type, you’ll need to use a discriminated field in your union
Duplicate of https://github.com/remix-run/remix/issues/7246