oscklm
oscklm16mo ago

v.union() prevents spreading array into function call

So i've become curious to as why v.union() requires the first 2 arguments to be directly passed, before being allowed to spread my mapped array? What i'm trying to do is make this more validator more dynamic, by using my const of categories and generating a validator from the const:
// The manual approach i started with, which works
export const manuallyDefinedValidator = v.union(
v.literal('DIY'),
v.literal('kunst'),
v.literal('musik'),
v.literal('sport'),
v.literal('science'),
v.literal('natur'),
v.literal('gaming'),
v.literal('mad'),
v.literal('tech'),
v.literal('viden'),
v.literal('nyheder'),
v.literal('samfund'),
v.literal('underholdning'),
);
// The manual approach i started with, which works
export const manuallyDefinedValidator = v.union(
v.literal('DIY'),
v.literal('kunst'),
v.literal('musik'),
v.literal('sport'),
v.literal('science'),
v.literal('natur'),
v.literal('gaming'),
v.literal('mad'),
v.literal('tech'),
v.literal('viden'),
v.literal('nyheder'),
v.literal('samfund'),
v.literal('underholdning'),
);
My dynamic approach to generating the validator
// constants/categories.ts
export const categories = [
'DIY',
'kunst',
'musik',
'sport',
'science',
'natur',
'gaming',
'mad',
'tech',
'viden',
'nyheder',
'samfund',
'underholdning',
] as const;

export type Category = (typeof categories)[number];
// constants/categories.ts
export const categories = [
'DIY',
'kunst',
'musik',
'sport',
'science',
'natur',
'gaming',
'mad',
'tech',
'viden',
'nyheder',
'samfund',
'underholdning',
] as const;

export type Category = (typeof categories)[number];
// A bit more dynamic IMO, but unsure if its overkill
export const categoryValidator = v.union(
v.literal('underholdning'),
v.literal('DIY'),
...categories.map((category) => v.literal(category)),
);
// A bit more dynamic IMO, but unsure if its overkill
export const categoryValidator = v.union(
v.literal('underholdning'),
v.literal('DIY'),
...categories.map((category) => v.literal(category)),
);
What i CANT do is this:
export const categoryValidator = v.union(...categories.map((category) => v.literal(category)));
export const categoryValidator = v.union(...categories.map((category) => v.literal(category)));
More context... Motivation for doing this, is that now i can just pass ´categories´ to my create video form, and render the categories in a select field, while also using the same categories const to generate my validator which i use in the createVideo mutation.
7 Replies
oscklm
oscklmOP16mo ago
// How the field in the form looks
<FormField
control={form.control}
name="category"
render={({ field }) => (
<FormItem>
<FormLabel>
<span>Category</span>
<span className="text-red-400">*</span>
</FormLabel>
<Select onValueChange={field.onChange} defaultValue={field.value}>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Select a category" />
</SelectTrigger>
</FormControl>
<SelectContent>
{categories.map((category) => (
<SelectItem
key={category}
value={category}
className="capitalize"
>
{category}
</SelectItem>
))}
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>
// How the field in the form looks
<FormField
control={form.control}
name="category"
render={({ field }) => (
<FormItem>
<FormLabel>
<span>Category</span>
<span className="text-red-400">*</span>
</FormLabel>
<Select onValueChange={field.onChange} defaultValue={field.value}>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Select a category" />
</SelectTrigger>
</FormControl>
<SelectContent>
{categories.map((category) => (
<SelectItem
key={category}
value={category}
className="capitalize"
>
{category}
</SelectItem>
))}
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>
Michal Srb
Michal Srb16mo ago
Not the answer to your question per sei, but I would consider using v.string() and moving the validation to inside your function, where you can use any logic you'd like. It might turn out that you don't even need a precise union type for category in your code (although you can still get it via validation inside your function).
oscklm
oscklmOP16mo ago
Thanks Michael. I can see what u mean, maybe a union type is a bit much here. Makes me curious, is there any specific use cases the union type was meant for. I’d love for some examples of how it’s used, if anyone care to share.
Michal Srb
Michal Srb16mo ago
It's generally useful similarly to TypeScript's | operator. Some examples of literal unions:
format: v.union(v.literal("text"), v.literal("giphy")),
format: v.union(v.literal("text"), v.literal("giphy")),
status: v.union(v.literal("queued"), v.literal("in_progress")),
status: v.union(v.literal("queued"), v.literal("in_progress")),
And you can use it with other types as well. I often use v.union(v.null(), .... as I prefer to work with explicit nulls over missing keys / undefineds. You can use union of object types in case the data has multiple "shapes".
oscklm
oscklmOP16mo ago
Thanks for sharing! Nice to have in mind
ian
ian16mo ago
The motivation for having 2+ args was to avoid accidental / mistaken usages. To avoid someone making a union with only one option. Your proposal isn’t overkill- you can slice off the first to elements if you wanted it to be less redundant though Thanks for the feedback- I agree it’s a bit awkward here! But yeah, I’d recommend union of literals for where you want validation and auto-complete hints / type errors. If the values are created by the app and possibly dynamic, I’d avoid a union. Removing a literal from that list would fail to deploy if any data had the old category
oscklm
oscklmOP16mo ago
Thanks Ian! This cleared up a lot for me

Did you find this page helpful?