#more laxist input types while keeping tight output types

15 messages · Page 1 of 1 (latest)

severe flower
#

I implemented a wrapper around the useForm param to avoid having to pass the whole defaultValues data structure when creating a form (which honestly is quite cumbersome when all values are undefined by default). i had to combine the types of the input schema and default values in order to stitch back the form types to make tsc happy. here's the function:

export function formConfig<Schema extends z.ZodObject, Defaults extends object>({
  defaultValues,
  schema,
  onSubmit,
}: {
  defaultValues?: Defaults
  schema: Schema
  onSubmit: (props: { value: z.output<Schema> & Defaults }) => void
}) {
  return {
    defaultValues: defaultValues as z.input<Schema> & Defaults,
    validators: {
      onSubmit: ({ value }) => {
        const result = schema.safeParse({
          ...defaultValues,
          ...value,
        })

        return result.success ? undefined : result.error
      },
    },
    onSubmit,
    onSubmitInvalid: (args) => console.log(args),
  }
}

used as:

const form = useForm(
  formConfig({
    defaultValues: {}, // becomes optional and can be partial,
    schema: someZodSchema,
    onSubmit({ value }) => {}, // value is union of defaultValues and z.output<schema>
  })
)

it does work well to one exception: i wished to add that the type representing the form's values (i guess, typeof defaultValues) would be wrapped Partial to support the pristine and reset states of the inputs from types PoV. When i change:

- defaultValues: defaultValues as z.input<Schema> & Defaults,
+ defaultValues: defaultValues as Partial<z.input<Schema>> & Defaults,

i get a type mismatch Types of property 'defaultValues' are incompatible.. I'm a bit lost at this error because i only declare (and cast) the defaultValues so the comparison with another defaultValues might not come from me.

  1. Is there a way to decouple the form values defaultValues from the rest of the lib
  2. Or is there another way to achieve what i want to do?
severe flower
#

I have found a satisfactory workaround but the problem comes from the fact that, in case of success, the transformed value returned by the validator is not kept and passed along to the onSubmit, so we have to effectively parse and transform twice. I think this part could be handled differently:

export function formConfig<Schema extends z.ZodObject, Defaults extends object>({
  defaultValues,
  schema,
  onSubmit,
}: {
    defaultValues?: Partial<Defaults>
  schema: Schema
  onSubmit: (props: { value: z.output<Schema> }) => void
}) {
  return {
    defaultValues: defaultValues as Partial<z.input<Schema> & Defaults>,
    validators: {
      onSubmit: ({ value }) => {
        // for validation
        const result = schema.safeParse({
          ...defaultValues,
          ...value,
        })

        return result.success ? undefined : result.error
      },
    },
    onSubmit: ({ value }) => {
      // for transformation
      const result = schema.safeParse({
        ...defaultValues,
        ...value,
      })

      if (result.success) {
        onSubmit({ value })
      }
    },
    onSubmitInvalid: (args) => console.log(args),
  }
}
#

Note: if you have nested form, recursive partial on defaultValues cast does the trick

severe flower
#

@rigid harbor any chance to have your insight

rigid harbor
#

with the current generics structure, there isn't a simple way around it

#

ideally, the constraint that should be satisfied is that

  • Schema is input: TSchema
  • defaultValues must implement TSchema, but can be wider

Simple in concept. Actually implementing that without breaking other things? Very unlikely

severe flower
#

😭

#

i can understand tho

rigid harbor
#

we're keeping it in mind of course, but for now, you'll need your own function to have the guard

rigid harbor
#

by the time onSubmit is reached, you can guarantee that the validators are satisfied, so you do not need safeParse.

severe flower
#

ah yes i see by design the schema can be different based on the callback

#

i can't wrap my mind around the usecase where the schema differs from onSubmit to onChange (for example)

rigid harbor
#

understandable, it doesn't feel good to use this way.

#

As far as performance goes, this is totally fine btw. The validation library will not be the bottleneck in this whole thing