I'm using ShadCN Vue with AutoForm
and zod
for schema validation. I'm trying to dynamically populate the select
options for a field named accounts
using an array of account objects.
My code:
<script setup lang="ts">
import { Button } from '#/components/ui/button';
import AutoForm from '#/components/ui/auto-form/AutoForm.vue';
import { z } from 'zod';
import { computed } from 'vue';
type Account = {
id?: number;
serviceId: number;
email: string;
password: string;
startDate: string;
endDate: string;
cost: number;
};
type Service = {
id?: number;
name: string;
pricePerMonth: number;
maxUsers: number;
accounts?: Account[];
};
// Fake data from DB
const allAccounts: Account[] = [
{
id: 1,
serviceId: 1, // fk
email: 'test@test.com',
password: '123456',
startDate: new Date(),
endDate: new Date(),
cost: 100,
},
];
const services: Service[] = [
{
id: 1,
name: 's 1',
pricePerMonth: 100,
maxUsers: 10,
accounts: [allAccounts[0]],
},
];
// The schema
const serviceSchema = z.object({
name: z
.string()
.min(1, 'Name is required')
.optional()
.default(services[0]?.name || ''),
pricePerMonth: z
.number()
.min(0, 'Price must be at least 0')
.max(10000, 'Price cannot exceed 10000')
.optional()
.default(
z
.preprocess((x) => Number(x), z.number())
.parse(services[0]?.pricePerMonth || 0)
),
maxUsers: z
.number()
.min(1, 'Max users must be at least 1')
.max(1000, 'Max users cannot exceed 1000')
.optional()
.default(services[0]?.maxUsers || 1),
accounts: z
.array(
z.object({
id: z.string(),
email: z.string(),
})
)
.default(
allAccounts.map((account: Account) => ({
id: String(account.id),
email: account.email,
}))
),
});
const fkIds = computed(() =>
allAccounts.map((account) => ({
value: String(account.id),
label: account.email,
}))
);
</script>
<template>
fkIds: {{ fkIds }}
<AutoForm
class="w-2/3 mx-auto mt-20 space-y-6"
:schema="serviceSchema"
@submit="onSubmit"
:field-config="{
accounts: {
component: 'select',
options: fkIds
? Object.fromEntries(fkIds.map((item) => [item.value, item.label]))
: [],
},
}"
>
<div>
<Button type="submit"> Update </Button>
</div>
</AutoForm>
</template>
Here is a live demo of what I tried https://stackblitz.com/edit/vue-shadcn-template-58gk2vtp?file=src%2FApp.vue
I want the accounts
field to render as a <select>
with dynamic options from fkIds
, but nothing shows up in the UI. I'm not sure if I'm binding the options correctly for ShadCN Vue’s AutoForm.
I want to properly render a select
input for the accounts
field and to populate it dynamically using the provided fkIds
Replace the accounts array with a single enum field like
accounts: z.enum(allAccounts.map(acc => String(acc.id)))
Remove or rename "<template #accounts>" as it's overriding the default field rendering.
<script setup lang="ts">
import { Button } from '#/components/ui/button';
import AutoForm from '#/components/ui/auto-form/AutoForm.vue';
import { z } from 'zod';
import { computed } from 'vue';
type Account = {
id?: number;
serviceId: number;
email: string;
password: string;
startDate: string;
endDate: string;
cost: number;
};
type Service = {
id?: number;
name: string;
pricePerMonth: number;
maxUsers: number;
accounts?: Account[];
};
// Fake data from DB
const allAccounts: Account[] = [
{
id: 1,
serviceId: 1,
email: 'test1@test.com',
password: '123456',
startDate: new Date(),
endDate: new Date(),
cost: 100,
},
{
id: 2,
serviceId: 1,
email: 'test2@test.com',
password: '654321',
startDate: new Date(),
endDate: new Date(),
cost: 120,
},
];
const services: Service[] = [
{
id: 1,
name: 's 1',
pricePerMonth: 100,
maxUsers: 10,
accounts: allAccounts,
},
];
// The schema
const serviceSchema = z.object({
name: z
.string()
.min(1, 'Name is required')
.optional()
.default(services[0]?.name || ''),
pricePerMonth: z
.number()
.min(0, 'Price must be at least 0')
.max(10000, 'Price cannot exceed 10000')
.optional()
.default(
z
.preprocess((x) => Number(x), z.number())
.parse(services[0]?.pricePerMonth || 0)
),
maxUsers: z
.number()
.min(1, 'Max users must be at least 1')
.max(1000, 'Max users cannot exceed 1000')
.optional()
.default(services[0]?.maxUsers || 1),
accounts: z.enum(allAccounts.map((acc) => String(acc.id))),
});
const fkIds = computed(() =>
allAccounts.map((account) => ({
value: String(account.id),
label: account.email,
}))
);
</script>
<template>
fkIds: {{ fkIds }}
<br />
s: {{ Object.fromEntries(fkIds.map((item) => [item.value, item.label])) }}
<AutoForm
:schema="serviceSchema"
@submit="onSubmit"
class="w-2/3 mx-auto space-y-6"
>
<Button type="submit">Update</Button>
</AutoForm>
</template>