Ability to pass dynamic values into Zod schema (qwik)
Closed this issue · 10 comments
I have a product form component and some products have different validation rules.
<QuantityInput min={1} max={10} multipleOf={2} />
I've tried passing props directly into the zod schema but I get error Internal server error: Qrl($) scope is not a function, but it's capturing local identifiers: props
. Would something like this be doable?
export const QuantityInput = component$((props) => {
const productForm = useFormStore<ProductForm>({
loader,
validate: zodForm$(
z.object({
qty: z.coerce
.number()
.min(props.min)
.max(props.max)
.multipleOf(props.multipleOf),
}),
),
});
return <></>;
})
Thanks again for your commitment to create an issue! I will update zodForm$
so you can pass a function. Then it should work. I will let you know when the update is available.
const productForm = useFormStore<ProductForm>({
loader,
validate: zodForm$(() => // Here is the difference
z.object({
qty: z.coerce
.number()
.min(props.min)
.max(props.max)
.multipleOf(props.multipleOf),
})
),
});
The new version is available. 🎉
I updated to the latest version and implemented the code as you wrote in a previous comment but when the validation triggers I get error Error: Code(14): Invoking 'use*()' method outside of invocation context.
import {useLexicalScope} from "/node_modules/.pnpm/@builder.io+qwik@0.24.0_undici@5.21.0/node_modules/@builder.io/qwik/core.mjs?v=7aacb625";
import {z} from "/node_modules/.pnpm/@builder.io+qwik-city@0.7.0_@builder.io+qwik@0.24.0/node_modules/@builder.io/qwik-city/index.qwik.mjs?v=7aacb625";
export const qty_input_component_loginForm_useFormStore_zodForm_uLT58SNrVtg = ()=>{
const [props] = useLexicalScope(); // <----------- ERROR POINTS HERE
return z.object({
qty: z.coerce.number().min(props.min).max(props.max).multipleOf(props.multipleOf)
});
}
It worked for me. Can you send me your code or a snippet of it?
Did you use component$
for your component?
This is the exact code
import { component$, useSignal } from '@builder.io/qwik';
import { zodForm$, Field, useFormStore } from '@modular-forms/qwik';
import { z } from '@builder.io/qwik-city';
import clsx from 'clsx';
interface Props {
min: number;
max: number;
multipleOf: number;
}
type QtyForm = { qty: number };
export default component$<Props>((props) => {
const loader = useSignal<QtyForm>({
qty: 1,
});
const productForm = useFormStore<QtyForm>({
loader,
validate: zodForm$(() =>
z.object({
qty: z.coerce
.number()
.min(props.min)
.max(props.max)
.multipleOf(props.multipleOf),
}),
),
validateOn: 'input',
});
return (
<>
<Field of={productForm} name="qty">
{(field, fieldProps) => (
<>
<input
{...fieldProps}
value={field.value}
class={clsx('form-input')}
type="text"
inputMode="numeric"
autoComplete="off"
/>
<div>Error: {field.error}</div>
</>
)}
</Field>
</>
);
});
@builder.io/qwik": "0.24.0"
@builder.io/qwik-city": "0.7.0"
@modular-forms/qwik": "^0.4.0"
I have investigated the problem. When a lexically scoped variable is accessed, Qwik executes useLexicalScope
under the hood. Since validation can be performed at various points outside of an invocation context, this breaks access to those variables and an error is thrown.
Unfortunately, I don't currently have a solution for this. However, I have two workarounds that may work for you. Instead of accessing props
within the formAction$
function, you can pass the whole schema via props
.
import { $, component$, type QRL } from '@builder.io/qwik';
import { Field, useFormStore, zodFormQrl } from '@modular-forms/qwik';
import { z, type ZodType } from 'zod';
interface Props {
schema: QRL<ZodType<any, any, any>>;
}
type QtyForm = { qty: number };
export const YourForm = component$<Props>((props) => {
const productForm = useFormStore<QtyForm>({
loader: { value: { qty: 1 } },
validate: zodFormQrl(props.schema),
validateOn: 'input',
});
return (
<Field of={productForm} name="qty">
{(field, fieldProps) => (
<>
<input
{...fieldProps}
value={field.value}
class="form-input"
type="text"
inputMode="numeric"
autoComplete="off"
/>
<div>Error: {field.error}</div>
</>
)}
</Field>
);
});
export default component$(() => (
<YourForm
schema={$(
z.object({
qty: z.coerce.number().min(1).max(2).multipleOf(5),
})
)}
/>
));
The second workaround is to forego validate
in useFormStore
with a Zod schema and do the validation via our internal validation functions.
import { component$ } from '@builder.io/qwik';
import {
custom$,
Field,
maxNumber,
minNumber,
useFormStore,
} from '@modular-forms/qwik';
interface Props {
min: number;
max: number;
multipleOf: number;
}
type QtyForm = { qty: number };
export const YourForm = component$<Props>((props) => {
const productForm = useFormStore<QtyForm>({
loader: { value: { qty: 1 } },
validateOn: 'input',
});
return (
<Field
of={productForm}
name="qty"
validate={[
minNumber(props.min, 'Number too small!'),
maxNumber(props.max, 'Number too big!'),
custom$(
(value) => !!value && value % props.multipleOf === 0,
`Not a multiple of ${props.multipleOf}.`
),
]}
>
{(field, fieldProps) => (
<>
<input
{...fieldProps}
value={field.value}
class="form-input"
type="text"
inputMode="numeric"
autoComplete="off"
/>
<div>Error: {field.error}</div>
</>
)}
</Field>
);
});
export default component$(() => <YourForm min={2} max={20} multipleOf={5} />);
I am unsure if I should reopen the issue, as I don't currently know if there is a solution to this problem. However, if there is any sign of it, I will definitely reopen it.
Thanks for the help! I haven't had a chance to work on it again but I will probably go with your second example.