Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

accept union type in generic component #429

Open
macmillen opened this issue May 13, 2024 · 5 comments
Open

accept union type in generic component #429

macmillen opened this issue May 13, 2024 · 5 comments
Labels
enhancement New feature or request

Comments

@macmillen
Copy link

macmillen commented May 13, 2024

Is your feature request related to a problem? Please describe.

To have generic components it's sometimes neccessary to pass down union types where only an intersection of the type is equal.

Demo:
https://www.sveltelab.dev/y7gmqoire0bl3u4?files=.%2Fsrc%2Flib%2Funion.svelte

Union component:

  export let superForm:
    | SuperForm<Infer<typeof eventSchema>>
    | SuperForm<Infer<typeof admissionSchema>>
    | SuperForm<Infer<typeof eventTemplateSchema>>;

Schemas:

export const eventSchema = z.object({
	name: z.string()
});
export const eventTemplateSchema = z.object({
	name: z.string(),
	id: z.string()
});
export const admissionSchema = z.object({
	name: z.string()
});

Generic input component

  export let superForm: SuperForm<T> | undefined = undefined;

But TypeScript complains if the schema types differ even slightly from each other.

Type 'SuperForm<{ name: string; }> | SuperForm<{ name: string; }> | SuperForm<{ name: string; id: string; }>' is not assignable to type 'SuperForm<{ name: string; }> | undefined'.
  Type 'SuperForm<{ name: string; id: string; }>' is not assignable to type 'SuperForm<{ name: string; }>'.
    The types of 'options.onUpdate' are incompatible between these types.
      Type '((event: { form: SuperValidated<{ name: string; id: string; }, any, { name: string; id: string; }>; formEl: HTMLFormElement; formElement: HTMLFormElement; cancel: () => void; result: Required<...>; }) => unknown) | undefined' is not assignable to type '((event: { form: SuperValidated<{ name: string; }, any, { name: string; }>; formEl: HTMLFormElement; formElement: HTMLFormElement; cancel: () => void; result: Required<...>; }) => unknown) | undefined'.
        Type '(event: { form: SuperValidated<{ name: string; id: string; }, any, { name: string; id: string; }>; formEl: HTMLFormElement; formElement: HTMLFormElement; cancel: () => void; result: Required<...>; }) => unknown' is not assignable to type '(event: { form: SuperValidated<{ name: string; }, any, { name: string; }>; formEl: HTMLFormElement; formElement: HTMLFormElement; cancel: () => void; result: Required<...>; }) => unknown'.

Describe the solution you'd like
It would be helpful to make the generics work with unions somehow

@macmillen macmillen added the enhancement New feature or request label May 13, 2024
@ciscoheat
Copy link
Owner

Does it work to put the union in the schemas, or in Infer?

SuperForm<Infer<typeof eventSchema | typeof admissionSchema>>
SuperForm<Infer<typeof eventSchema> | Infer<typeof admissionSchema>>

@macmillen
Copy link
Author

Unfortunately not. I tried all variations 😕

@macmillen
Copy link
Author

I updated the demo with your suggestion but it still fails

Error: Type 'SuperForm<{ name: string; desc: string | null; }, any>' is not assignable to type 'SuperForm<Infer<ZodObject<{ name: ZodString; id: ZodString; }, "strip", ZodTypeAny, { name: string; id: string; }, { name: string; id: string; }> | ZodObject<...>>>'.
  Types of property 'options' are incompatible.
    Type 'Partial<{ id: string; applyAction: boolean; invalidateAll: boolean | "force"; resetForm: boolean | (() => boolean); scrollToError: boolean | ScrollIntoViewOptions | "auto" | "smooth" | "off"; ... 24 more ...; legacy: boolean; }>' is not assignable to type 'Partial<{ id: string; applyAction: boolean; invalidateAll: boolean | "force"; resetForm: boolean | (() => boolean); scrollToError: boolean | ScrollIntoViewOptions | "auto" | "smooth" | "off"; ... 24 more ...; legacy: boolean; }> | Partial<...>'.
      Type 'Partial<{ id: string; applyAction: boolean; invalidateAll: boolean | "force"; resetForm: boolean | (() => boolean); scrollToError: boolean | ScrollIntoViewOptions | "auto" | "smooth" | "off"; ... 24 more ...; legacy: boolean; }>' is not assignable to type 'Partial<{ id: string; applyAction: boolean; invalidateAll: boolean | "force"; resetForm: boolean | (() => boolean); scrollToError: boolean | ScrollIntoViewOptions | "auto" | "smooth" | "off"; ... 24 more ...; legacy: boolean; }>'. Two different types with this name exist, but they are unrelated.
        Types of property 'onUpdate' are incompatible.
          Type '((event: { form: SuperValidated<{ name: string; desc: string | null; }, any, { name: string; desc: string | null; }>; formEl: HTMLFormElement; formElement: HTMLFormElement; cancel: () => void; result: Required<...>; }) => unknown) | undefined' is not assignable to type '((event: { form: SuperValidated<{ name: string; }, any, { name: string; }>; formEl: HTMLFormElement; formElement: HTMLFormElement; cancel: () => void; result: Required<...>; }) => unknown) | undefined'.
            Type '(event: { form: SuperValidated<{ name: string; desc: string | null; }, any, { name: string; desc: string | null; }>; formEl: HTMLFormElement; formElement: HTMLFormElement; cancel: () => void; result: Required<...>; }) => unknown' is not assignable to type '(event: { form: SuperValidated<{ name: string; }, any, { name: string; }>; formEl: HTMLFormElement; formElement: HTMLFormElement; cancel: () => void; result: Required<...>; }) => unknown'. (ts)

<Union {superForm} />

@macmillen
Copy link
Author

I though maybe a way to solve this would be to use generics but it also doesn't work. This time the field prop complains.
(See demo - component name is union2.svelte)

<script lang="ts" generics="T extends { name: string }">
	import type { SuperForm } from 'sveltekit-superforms/client';
	import Input from './input.svelte';

	export let superForm: SuperForm<T>;

	$: ({ form } = superForm);
</script>

<Input {superForm} field="name" />
Type '"name"' is not assignable to type 'FormPathLeaves<T>'.
  Type '"name"' is not assignable to type 'StringPath<T, { filter: "leaves"; objAppend: never; path: ""; type: any; }>'.
ts

@ciscoheat
Copy link
Owner

You can use either z.union, or define the type as you did:

export const schema = z.union([eventTemplateSchema, eventSchema, admissionSchema]);

export type UnionSchema =
	| Infer<typeof eventSchema>
	| Infer<typeof admissionSchema>
	| Infer<typeof eventTemplateSchema>;

Then use the superForm type parameter to get the correct type for the schema:

+page.svelte

<script lang="ts">
	import Union from './union.svelte';
	import { superForm as _superForm } from '$lib/index.js';
	import type { UnionSchema } from './schemas.js';

	export let data;

	const superForm = _superForm<UnionSchema>(data.form);
</script>

<Union {superForm} />

union.svelte

<script lang="ts">
	import type { SuperForm } from '$lib/index.js';
	import { type UnionSchema } from './schemas.js';
	import Input from './input.svelte';

	export let superForm: SuperForm<UnionSchema>;
</script>

<Input {superForm} field="name" />

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants