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

Validate multiple fields against each other using Zod #116

Closed
thenbe opened this issue Mar 16, 2022 · 6 comments
Closed

Validate multiple fields against each other using Zod #116

thenbe opened this issue Mar 16, 2022 · 6 comments
Labels
enhancement New feature or request

Comments

@thenbe
Copy link

thenbe commented Mar 16, 2022

It's common to validate fields based on other fields. For example, a form with a start date and end date.

The way to do this with Zod (that I know of) is like the snippet below. However, typescript will complain because validator will only accept a schema of type ZodObject, whereas here the schema is of type ZodEffects.

export const schema = z
	.object({
		start: z.date(),
		end: z.date(),
	})
	.refine((val) => val.start < val.end, {
		path: ['start'],
		message: 'Start date must be before end date',
	})
	.refine((val) => val.start < val.end, {
		path: ['end'],
		message: 'End date must be after start date',
	});

const { form } = createForm({
  // ...
  validate: validateSchema(schema)
  // ...
});

I've had some success with manually casting like below. It doesn't seem to work all the time though. Some of my forms fail to validate properly even when doing this. Not sure why yet.

-		validate: validateSchema(schema),
+		validate: validateSchema(schema as z.AnyZodObject),
@thenbe thenbe added the enhancement New feature or request label Mar 16, 2022
@pablo-abc
Copy link
Owner

Hey! Do you have an example of this failing even with casting? As far as I can see, the API we use internally is the same (for it to have a parseAsync method that throws an errors object with an issues array).

@thenbe
Copy link
Author

thenbe commented Mar 16, 2022

I've dug a bit into this and while I did manage to recreate the issue, I'm starting to think that this is a zod thing not a felte thing. Even after reading through the zod docs, I can't seem to tell if this is how zod works or if it's a bug.

The issue:

The validation that compares start & end won't run until all other fields have a value. In the snippet below, you have to input a price and a quantity in order to compare start vs end.

A workaround would be to always provide initialValues of all the fields in the zod schema.

<script lang="ts">
  import reporter from '@felte/reporter-tippy';
  import { validateSchema } from '@felte/validator-zod';
  import { createForm } from 'felte';
  import 'tippy.js/dist/tippy.css';
  import { z } from 'zod';

  const schema = z
    .object({
      price: z.number(),
      quantity: z.number(),
      start: z.preprocess((arg) => {
        if (typeof arg === 'string' || arg instanceof Date) return new Date(arg);
      }, z.date()),
      end: z.preprocess((arg) => {
        if (typeof arg === 'string' || arg instanceof Date) return new Date(arg);
      }, z.date())
    })
    .refine((val) => val.start < val.end, {
      path: ['start'],
      message: 'Start date must be before end date'
    })
    .refine((val) => val.start < val.end, {
      path: ['end'],
      message: 'End date must be after start date'
    });

  const { form } = createForm({
    initialValues: {
      start: '2022-01-15',
      end: '2022-01-13'
    },
    extend: reporter(),
    validate: validateSchema(schema as unknown as z.AnyZodObject)
  });
</script>

<form use:form>
  <input type="number" name="price" />
  <input type="number" name="quantity" />
  <input type="date" name="start" />
  <input type="date" name="end" />
  <button type="submit">Submit</button>
</form>

@pablo-abc
Copy link
Owner

pablo-abc commented Mar 16, 2022

Might have to do with the fact that Zod makes every property optional by default? So maybe no validation runs if the value does not exist? Although I'm not sure if that's also supposed to be the behaviour with refinements.

I mistook Zod with Yup here so this is not the reason the issue is happening. But I see your comment below, so there's nothing to do besides using initialValues always.

@thenbe
Copy link
Author

thenbe commented Mar 16, 2022

colinhacks/zod#479

I should've done a better job searching. It seems like this is how zod works. There are a few workarounds in that thread as well.

@thenbe thenbe closed this as completed Mar 16, 2022
@pablo-abc
Copy link
Owner

Thanks for the issue regardless! I did fix the type of the expected schema, so you shouldn't need to cast the value on the latest version.

@alexkubica
Copy link

@ambiguous48 what worked for you?

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

3 participants