fabian-hiller / modular-forms

The modular and type-safe form library for SolidJS, Qwik and Preact
https://modularforms.dev
MIT License
1k stars 53 forks source link

Qwik: Form not working when schema is declared inside component$ #167

Open DevHusariaSolutions opened 9 months ago

DevHusariaSolutions commented 9 months ago

Example in docs is omitting important part of validation libraries - custom errors. If we have multi-lang website content of such errors is derived by hook function which returns translated content, but hook needs to be used inside component, so schema also needs to be defined in component. When doing so I gets error

r: Qrl($) scope is not a function, but it's capturing local identifiers: formSchema

where formSchema is variable name of my validation schema.

Moving formSchema outside component works, but ofc I won't be able to define custom errors for it.

Please publish advanced example of Your solution.

fabian-hiller commented 9 months ago

Just wrap your schema with zodForm$ or valiForm$ and pass the returned value to .validate of useForm. Please share your code if you need more help. If you are using Valibot, we plan to improve i18n to allow you to define your translations and schemas globally.

export default component$(() => {
  const LoginSchema = valiForm$(
    v.object({
      email: v.string([
        v.minLength(1, 'Please enter your email.'),
        v.email('The email address is badly formatted.'),
      ]),
      password: v.string([
        v.minLength(1, 'Please enter your password.'),
        v.minLength(8, 'You password must have 8 characters or more.'),
      ]),
    })
  );

  // Use login form
  const [loginForm, { Form, Field }] = useForm<LoginForm>({
    loader: useFormLoader(),
    action: useFormAction(),
    validate: LoginSchema,
  });

 // More code here...
DevHusariaSolutions commented 9 months ago

I've already tried this, no result of change. I don't use loader as function, I use plain object.

To test it You can use

import { $, component$, useStore, type QRL } from '@builder.io/qwik'
import type { SubmitHandler } from '@modular-forms/qwik'
import { formAction$, useForm, valiForm$ } from '@modular-forms/qwik'
import { email, minLength, object, string, type Input } from 'valibot'

export default component$(() => {
 const formSchema=object({
    email: string([minLength(1, 'Please enter your email.'), email('form.validation.emailInvalid')]),
    firstName: string([
      minLength(1, 'Please enter your password.'),
      minLength(8, 'Your password must have 8 characters or more.'),
    ]),
  })
  const validate = valiForm$(formSchema)
  type BootcampRegisterForm = Input<typeof formSchema>

  const handleSubmit: QRL<SubmitHandler<BootcampRegisterForm>> = $((values) => {
    // Runs on client
    console.log(values)
  })
  const [, { Form, Field }] = useForm<BootcampRegisterForm>({
    loader: { value: { firstName: '', email: '' } },
    // action: useFormAction(),
    validate,
  })

  return (
    <Form onSubmit$={handleSubmit}>
      <Field name="firstName">
        {(field, props) => <input required {...props} {...field} label="First name" type="text" />}
      </Field>
      <Field name="email">
        {(field, props) => <input required {...props} {...field} label="E-mail" type="email" />}
      </Field>
      <button type="submit">Login</button>
    </Form>
  )
});
DevHusariaSolutions commented 9 months ago

Moving schema outside component with translation hooks gives inproper behaviour - error is not translated, only key of translation is returned

const t = inlineTranslate()

const formSchema = z.object({
  firstName: z.string().min(3, { message: t('form.validation.nameInvalid') }),
  email: z
    .string({ required_error: t('form.validation.emailRequired') })
    .email({ message: t('form.validation.emailInvalid') }),
})

export const RegisterBootcampForm = component$(() => {
  const useFormAction = formAction$<BootcampRegisterForm>((values) => {
    // TODO: ADD BACKEND ACTION
    console.log(values)
  }, zodForm$(formSchema))
  type BootcampRegisterForm = z.infer<typeof formSchema>

  const handleSubmit: QRL<SubmitHandler<BootcampRegisterForm>> = $((values) => {
    // Runs on client
    console.log(values)
  })
  const [, { Form, Field }] = useForm<BootcampRegisterForm>({
    loader: { value: { firstName: '', email: '' } },
    action: useFormAction(),
    validate: zodForm$(formSchema),
  })
DevHusariaSolutions commented 9 months ago

And another test failed:

  const t = inlineTranslate()

  type BootcampRegisterForm = { firstName: string; email: string }

  const handleSubmit: QRL<SubmitHandler<BootcampRegisterForm>> = $((values) => {
    // Runs on client
    console.log(values)
  })
  const [, { Form, Field }] = useForm<BootcampRegisterForm>({
    loader: { value: { firstName: '', email: '' } },
  })

  return (
    <Form onSubmit$={handleSubmit}>
      <Field name="firstName" validate={minLength(3, t('form.validation.nameInvalid'))}>
  Resigning from using formSchema and passing only validate stuff Field component also prints untranslated error...
fabian-hiller commented 9 months ago

It only works for me if I put the schema inside zodForm$. Not just the variable that stores the schema. But this makes it harder to infer the input type of the schema.

DevHusariaSolutions commented 9 months ago

Ok, but can we do something about that? Schema is just object, which should be composable inside component in case we would like to:

fabian-hiller commented 9 months ago

I will investigate this issue but it will take time. I plan to rewrite Modular Forms in February. Unfortunately, I don't have a perfect solution for you at the moment. You can try to define the error messages globally in Zod with an error map. Alternatively, you can also try switching to Valibot. I plan to implement our i18n feature by the end of this week. This should allow you to define your schema globally.

DevHusariaSolutions commented 8 months ago

For Valibot it was the same. No problem, currently I'm using this for landing page, which is not problematic to handle outside it (especially, that user shouldn't start typeing data in form controls before switching to another language first).

fabian-hiller commented 7 months ago

Have you tried Valibot's new i18n feature? https://valibot.dev/guides/introduction/