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

Ability to pass dynamic values into Zod schema (qwik) #38

Closed
tuurbo opened this issue Mar 25, 2023 · 10 comments
Closed

Ability to pass dynamic values into Zod schema (qwik) #38

tuurbo opened this issue Mar 25, 2023 · 10 comments
Assignees
Labels
enhancement New feature or request

Comments

@tuurbo
Copy link

tuurbo commented Mar 25, 2023

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 <></>;
})
@fabian-hiller fabian-hiller self-assigned this Mar 25, 2023
@fabian-hiller fabian-hiller added the enhancement New feature or request label Mar 25, 2023
@fabian-hiller
Copy link
Owner

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),
    })
  ),
});

@fabian-hiller
Copy link
Owner

The new version is available. 🎉

@tuurbo
Copy link
Author

tuurbo commented Mar 25, 2023

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)
    });
}

@fabian-hiller
Copy link
Owner

It worked for me. Can you send me your code or a snippet of it?

@fabian-hiller
Copy link
Owner

Did you use component$ for your component?

@tuurbo
Copy link
Author

tuurbo commented Mar 25, 2023

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>
    </>
  );
});

@tuurbo
Copy link
Author

tuurbo commented Mar 25, 2023

@builder.io/qwik": "0.24.0"
@builder.io/qwik-city": "0.7.0"
@modular-forms/qwik": "^0.4.0"

@fabian-hiller
Copy link
Owner

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} />);

@fabian-hiller
Copy link
Owner

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.

@tuurbo
Copy link
Author

tuurbo commented Mar 28, 2023

Thanks for the help! I haven't had a chance to work on it again but I will probably go with your second example.

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