T
TanStack2mo ago
absent-sapphire

Type error in createServerValidate when using discriminated union schemas

When using discriminated unions as schemas, the createServerValidate function gives a type error. If I cast the defaultValues in formOptions, the error goes away. Is this the intended approach or is there some more type safe way to use discriminated unions? Stackblitz: https://stackblitz.com/edit/tanstack-form-tnj3qpfd?file=src%2Findex.tsx
import { createServerValidate, formOptions } from '@tanstack/react-form/remix';
import { z } from 'zod';

const UnionFoo = z.object({ discriminator: z.literal('foo'), foo: z.string() });

const UnionBar = z.object({ discriminator: z.literal('bar') });

const ExampleSchema = z.discriminatedUnion('discriminator', [
UnionFoo,
UnionBar,
]);

const formDefaultValues: z.input<typeof ExampleSchema> = {
discriminator: 'foo',
foo: 'example',
};

const formOpts = formOptions({
/* Works with the casting below. Is this the preferred way? */
defaultValues: formDefaultValues /* as z.input<typeof ExampleSchema> */,
validators: {
onChange: ExampleSchema,
},
});

// Type error
const serverValidate = createServerValidate({
...formOpts,
onServerValidate: (values) => {
console.log(values);
},
});
import { createServerValidate, formOptions } from '@tanstack/react-form/remix';
import { z } from 'zod';

const UnionFoo = z.object({ discriminator: z.literal('foo'), foo: z.string() });

const UnionBar = z.object({ discriminator: z.literal('bar') });

const ExampleSchema = z.discriminatedUnion('discriminator', [
UnionFoo,
UnionBar,
]);

const formDefaultValues: z.input<typeof ExampleSchema> = {
discriminator: 'foo',
foo: 'example',
};

const formOpts = formOptions({
/* Works with the casting below. Is this the preferred way? */
defaultValues: formDefaultValues /* as z.input<typeof ExampleSchema> */,
validators: {
onChange: ExampleSchema,
},
});

// Type error
const serverValidate = createServerValidate({
...formOpts,
onServerValidate: (values) => {
console.log(values);
},
});
StackBlitz
Form Simple Example (duplicated) - StackBlitz
Run official live example code for Form Simple, created by Tanstack on StackBlitz
10 Replies
ambitious-aqua
ambitious-aqua2mo ago
The last paragraph should be the solution to your problem
absent-sapphire
absent-sapphireOP2mo ago
I updated the stackblitz. Still getting the same error.
fascinating-indigo
fascinating-indigo2mo ago
doesn't list a type error for me. Which line causes the error for you?
No description
absent-sapphire
absent-sapphireOP2mo ago
The type error only appears after typing something in to the editor, seems like a bug with stackblitz. Lines 27-32 turn squiggly Here's the error:
Argument of type '{ onServerValidate: (values: { value: { discriminator: "foo"; foo: string; }; formApi: FormApi<{ discriminator: "foo"; foo: string; }, any, any, any, any, any, any, any, any, any>; signal: AbortSignal; }) => void; defaultValues: { ...; }; validators: { ...; }; }' is not assignable to parameter of type 'CreateServerValidateOptions<{ discriminator: "foo"; foo: string; }, FormValidateOrFn<{ discriminator: "foo"; foo: string; }> | undefined, FormValidateOrFn<{ discriminator: "foo"; foo: string; }> | undefined, ... 6 more ..., unknown>'.
The types of 'validators.onChange' are incompatible between these types.
Type 'ZodDiscriminatedUnion<"discriminator", [ZodObject<{ discriminator: ZodLiteral<"foo">; foo: ZodString; }, "strip", ZodTypeAny, { discriminator: "foo"; foo: string; }, { discriminator: "foo"; foo: string; }>, ZodObject<...>]>' is not assignable to type 'FormValidateOrFn<{ discriminator: "foo"; foo: string; }> | undefined'.
Type 'ZodDiscriminatedUnion<"discriminator", [ZodObject<{ discriminator: ZodLiteral<"foo">; foo: ZodString; }, "strip", ZodTypeAny, { discriminator: "foo"; foo: string; }, { discriminator: "foo"; foo: string; }>, ZodObject<...>]>' is not assignable to type 'StandardSchemaV1<{ discriminator: "foo"; foo: string; }, unknown>'.
Argument of type '{ onServerValidate: (values: { value: { discriminator: "foo"; foo: string; }; formApi: FormApi<{ discriminator: "foo"; foo: string; }, any, any, any, any, any, any, any, any, any>; signal: AbortSignal; }) => void; defaultValues: { ...; }; validators: { ...; }; }' is not assignable to parameter of type 'CreateServerValidateOptions<{ discriminator: "foo"; foo: string; }, FormValidateOrFn<{ discriminator: "foo"; foo: string; }> | undefined, FormValidateOrFn<{ discriminator: "foo"; foo: string; }> | undefined, ... 6 more ..., unknown>'.
The types of 'validators.onChange' are incompatible between these types.
Type 'ZodDiscriminatedUnion<"discriminator", [ZodObject<{ discriminator: ZodLiteral<"foo">; foo: ZodString; }, "strip", ZodTypeAny, { discriminator: "foo"; foo: string; }, { discriminator: "foo"; foo: string; }>, ZodObject<...>]>' is not assignable to type 'FormValidateOrFn<{ discriminator: "foo"; foo: string; }> | undefined'.
Type 'ZodDiscriminatedUnion<"discriminator", [ZodObject<{ discriminator: ZodLiteral<"foo">; foo: ZodString; }, "strip", ZodTypeAny, { discriminator: "foo"; foo: string; }, { discriminator: "foo"; foo: string; }>, ZodObject<...>]>' is not assignable to type 'StandardSchemaV1<{ discriminator: "foo"; foo: string; }, unknown>'.
The types of ''~standard'.types' are incompatible between these types.
Type 'Types<{ discriminator: "foo"; foo: string; } | { discriminator: "bar"; foo: string; }, { discriminator: "foo"; foo: string; } | { discriminator: "bar"; foo: string; }> | undefined' is not assignable to type 'StandardSchemaV1Types<{ discriminator: "foo"; foo: string; }, unknown> | undefined'.
Type 'Types<{ discriminator: "foo"; foo: string; } | { discriminator: "bar"; foo: string; }, { discriminator: "foo"; foo: string; } | { discriminator: "bar"; foo: string; }>' is not assignable to type 'StandardSchemaV1Types<{ discriminator: "foo"; foo: string; }, unknown>'.
Types of property 'input' are incompatible.
Type '{ discriminator: "foo"; foo: string; } | { discriminator: "bar"; foo: string; }' is not assignable to type '{ discriminator: "foo"; foo: string; }'.
Type '{ discriminator: "bar"; foo: string; }' is not assignable to type '{ discriminator: "foo"; foo: string; }'.
Types of property 'discriminator' are incompatible.
Type '"bar"' is not assignable to type '"foo"'.
The types of ''~standard'.types' are incompatible between these types.
Type 'Types<{ discriminator: "foo"; foo: string; } | { discriminator: "bar"; foo: string; }, { discriminator: "foo"; foo: string; } | { discriminator: "bar"; foo: string; }> | undefined' is not assignable to type 'StandardSchemaV1Types<{ discriminator: "foo"; foo: string; }, unknown> | undefined'.
Type 'Types<{ discriminator: "foo"; foo: string; } | { discriminator: "bar"; foo: string; }, { discriminator: "foo"; foo: string; } | { discriminator: "bar"; foo: string; }>' is not assignable to type 'StandardSchemaV1Types<{ discriminator: "foo"; foo: string; }, unknown>'.
Types of property 'input' are incompatible.
Type '{ discriminator: "foo"; foo: string; } | { discriminator: "bar"; foo: string; }' is not assignable to type '{ discriminator: "foo"; foo: string; }'.
Type '{ discriminator: "bar"; foo: string; }' is not assignable to type '{ discriminator: "foo"; foo: string; }'.
Types of property 'discriminator' are incompatible.
Type '"bar"' is not assignable to type '"foo"'.
fascinating-indigo
fascinating-indigo2mo ago
TypeScript is being too strict with you what ends up happening is TS thinks "this value never changes, so it's not z.input<typeof ExampleSchema, but it's only the foo variant really strange. But one way to 'fix' it is to reassign it
const formDefaultValues: z.input<typeof ExampleSchema> = {
discriminator: 'foo',
foo: 'example',
} as z.input<typeof ExampleSchema>;
const formDefaultValues: z.input<typeof ExampleSchema> = {
discriminator: 'foo',
foo: 'example',
} as z.input<typeof ExampleSchema>;
absent-sapphire
absent-sapphireOP2mo ago
Yeah, what makes this extra weird is that the type definition seems to be the full union when hovering the variable definition but then when hovering the value inside formOptions the type is narrowed down.
No description
No description
fascinating-indigo
fascinating-indigo2mo ago
yeah, and passing it directly also appears to be the union
No description
fascinating-indigo
fascinating-indigo2mo ago
so I guess it's an issue with formOptions? deserves some further investigating I reckon can you create a GH issue for checking generated formOptions types vs. passing directly?
absent-sapphire
absent-sapphireOP2mo ago
Sure 👍
fascinating-indigo
fascinating-indigo2mo ago
I found an issue on TypeScript's repo that appears to address this concern. Marked as not planned :Sadge: https://github.com/microsoft/TypeScript/issues/61789 @Lomant
GitHub
Skip assignment narrowing when declaring & initializing a variable ...
🔍 Search Terms declare, assign, declaration, assignment, narrow, narrowing, union, variable, &quot;control flow analysis&quot; ✅ Viability Checklist This wouldn&#39;t be a breaking change in existi...

Did you find this page helpful?