-
-
Notifications
You must be signed in to change notification settings - Fork 201
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
How to control 2 fields equal or not #76
Comments
For this you can use our pipeline feature: https://valibot.dev/guides/pipelines/ import { custom, email, minLength, object, string } from 'valibot';
const SignUpSchema = object(
{
email: string([
minLength(1, 'Please enter your email.'),
email('The email address is badly formatted.'),
]),
password1: string([
minLength(1, 'Please enter your password.'),
minLength(8, 'You password must have 8 characters or more.'),
]),
password2: string([minLength(1, 'Please repeat the password once.')]),
},
[
custom(
({ password1, password2 }) => password1 === password2,
'The passwords do not match.'
),
]
); |
I tried this with react-hook-form but I don't get " the passwords do not match." error |
Have a look at this comment: #5 (comment) I suspect that the problem has to do with the error being issued as an issue of the object and not the second password. Do you know how Zod solves this problem? |
Thanks! I did this: export const RegisterSchema = object(
{
email: string([
minLength(1, "Please enter your email."),
email("The email address is badly formatted."),
]),
password: string([
minLength(1, "Please enter your password."),
minLength(8, "You password must have 8 characters or more."),
]),
confirmPassword: string([
minLength(1, "Please enter your password."),
minLength(8, "You password must have 8 characters or more."),
]),
},
[
(input) => {
if (input.password !== input.confirmPassword) {
throw new ValiError([
{
reason: "string",
validation: "custom",
origin: "value",
message: "Passwords do not match.",
input: input.confirmPassword,
path: [
{
schema: "object",
input: input,
key: "confirmPassword",
value: input.confirmPassword,
},
],
},
])
}
return input
},
]
) |
I will try to improve the DX for this use case before v1. |
@fabian-hiller, I have added PR to implement |
Do you have any ideas on how we could integrate this into Valibot's API design? |
Few ideas:
|
@fabian-hiller after some consideration, I think the cleanliest solution in terms of API could be something like this, inspired by Zod: const schema = object({
name: string(),
password: string(),
confirmPassword: string(),
}, [
{
type: 'pre', // Run before the validation
transform: (data: unknown) => data.password === data.confirmPassword, // Input will be unknown
},
{
type: 'post', // Run after validation (default)
transform: (data: Typed) => data.password === data.confirmPassword, // Input will be typed
// Allow specifying path and error message instead of throwing validation error
path: ["confirmPassword"],
message: "Both password and confirmation must match",
},
// Existing pipes are supported. Are the same as 'post' pipes
custom((data) => data.password === data.confirmPassword),
{
// Object schema only
type: 'partial', // Run after successfull validation of specified fields
fields: ['password', 'confirmPassword'], // Array elements can be typed
transform: (data: Pick<Typed, 'password' | 'confirmPassword'>) => data.password === data.confirmPassword, // Input will contain specified fields
},
]) |
Thanks a lot! I love to see code examples like yours. It helps to visualize the final implementation. Let's use this issue to gather more ideas. Then once I have the time to think about it in detail, we can move on to implementation. |
export const RegisterSchema = object({
body: object({
email: string([minLength(1), email()]),
password: string([
minLength(1, 'Please enter your password.'),
minLength(8, 'You password must have 8 characters or more.'),
]),
confirmPass: string([
minLength(1, 'Please repeat the password once.')
])
}, [(input) => {
if(input.password !== input.confirmPass) {
return {
issue: {
validation: 'custom',
message: 'The passwords do not match.',
input
}
}
}
return {output: input}
}])
}); |
For me returning an "issue" object freezes the browser tab completely |
Which Valibot version are you using? Can you share a reproduction on StackBlitz? |
If you are using Valibot with a form library, it may also be because different versions that are not compatible are being used. |
I think this may be the issue, I am using react hook forms with valibot resolver. They are using valibot version: |
I am not sure if that is the problem. Valibot is only registered as a peer dependency at React Form Hooks. Means that the version you specify in your package.json should be used. Can you provide me a minimal reproduction e.g. via StackBlitz? Then I can investigate the problem. |
@Demivan thanks again for your ideas. Maybe you can give feedback on this issue as well. Maybe we can avoid having to implement a pre-pipeline with a |
I was able to validate a field based on other field like this ↓ const TripInformationSchema = object(
{
departureCountry: string([minLength(2, 'Should have atleast 2 characters')]),
destinationCountry: string([minLength(2, 'Should have atleast 2 characters')]),
departureDate: string([
isoDate(),
custom(
(date) => isAfter(new Date(date), new Date()) || isToday(new Date(date)),
"The Departure Date must be on or after today's date."
)
]),
returnDate: string([isoDate()]),
numberOfTravelers: enumType(['1', '2'], 'Invalid selection')
},
[
(input) => {
const isSuccess =
isAfter(new Date(input.returnDate), new Date(input.departureDate)) ||
isSameDay(new Date(input.departureDate), new Date(input.returnDate))
if (!isSuccess) {
return {
issues: [
{
validation: 'custom',
message: 'The Return Date must be on or after the Departure Date',
input,
path: [{ schema: 'object', input: input, key: 'returnDate', value: input.returnDate }]
}
]
}
}
return { output: input }
}
]
) This verifies the form's |
Good point. Thanks for pointing that out. Is your concern that we need to run import { custom, minLength, object, string, transform } from 'valibot';
const Schema = object(
{
key: transform(string([minLength(3)]), (input) => input.length),
},
[
custom((input) => {
// Is `input.key` a number or a string when `minLength` fails?
console.log(input);
return input.key >= 3;
}),
]
); If we perform transform anyway, we need to document it well. I have investigated this with Zod in v3.22.4. Zod skips import { z } from "zod";
const Schema = z
.object({
key: z
.string()
.min(3)
.transform((input) => input.length),
})
.refine((input) => {
// Type of `input.key` is `string` and therefore invalid if `min` fails!
console.log(input);
return input.key >= 3;
}); |
Yes, and it is not just The proposed solution sounds ok on paper, but there are too many edge cases in my opinion. |
How would you proceed here? |
I think, to compare two fields in an object schema, those two fields need to be valid first. I propose in addition to So regular This is pretty much what I have proposed with I can try sketching a pull request. |
Good idea! Maybe we can build that into |
@fabian-hiller Added a proof of concept implementation of |
This approach is not working for me. |
@skotenko can you share your schema with us? Your problem is probably related to #76 (comment). |
@fabian-hiller Yeah, thanks for the answer. As I understood, there is no possibility to run custom earlier for now? |
Yes, that is true. But hopefully I will find a final solution for this in the next few weeks. |
My opinion would be, in addition to @Demivan 's suggestion, would be to just have a 2nd argument passed into custom validators that have all the fields values so far. If you want to be able to specify "hey validate these other fields first before mine", ok, but that's added on top functionality. First and foremost let us read the values of other fields. I don't see that violating your API's paradigm. |
@JortsEnjoyer0 the problem is that I think that there is no way to make it type safe because the |
Previously, the pipelines of complex schemas were only executed if there were no issues. With the new version v0.22.0, pipelines are now always executed if the input matches the data type of the schema, even if issues have already occurred. In addition, the new method In summary, it is now possible to compare two fields, for example import { custom, email, forward, minLength, object, string } from 'valibot';
const RegisterSchema = object(
{
email: string([
minLength(1, 'Please enter your email.'),
email('The email address is badly formatted.'),
]),
password1: string([
minLength(1, 'Please enter your password.'),
minLength(8, 'Your password must have 8 characters or more.'),
]),
password2: string(),
},
[
forward(
custom(
(input) => input.password1 === input.password2,
'The two passwords do not match.'
),
['password2']
),
]
); I look forward to hearing your feedback on this solution. |
I am trying to implement this with a conditionally optional field, I can't seem to get it working I have (within a schema object) requiredRadio: enum_(RADIO_ENUM),
conditionalRadio: enum_(RADIO_ENUM),
...
forward(
custom(
(input) => input.requiredRadio === RADIO_ENUM.option1 && !input.conditionalRadio,
'Please select a value'
),
['conditonalRadio']
), I have tried changing
|
@TepidDrink can you provide me with a working schema so I can run and test it on my machine? |
import { parse, object, string, enum_, optional, forward, custom } from 'valibot';
enum RADIO_ENUM {
'Yes',
'No',
};
const DummySchema = object(
{
stringVal: string('Provide a value'),
requiredRadio: enum_(RADIO_ENUM),
conditionalRadio: optional(enum_(RADIO_ENUM)),
},
[
forward(
custom(
(input) => input.requiredRadio === RADIO_ENUM.Yes && !input.conditionalRadio,
'Please select a value'
),
['conditionalRadio']
),
]
);
parse(DummySchema, {requiredRadio: 'Yes'}); The concept here is that when requiredRadio is 'yes', the user needs to provide the conditionalRadio (I also threw in stringVal to show that the two validations are dealt with separately). Ideally I'd like two error messages here: the one for stringVal and the one for conditionalRadio simultaneously, but instead it deals with stringVal only I guess because its in a pipeline for afterwards. Removing/providing stringVal under current valibot implementation, conditionalRadio gets the generic 'Invalid Value' error message rather than 'Please select a value' which is the main issue. |
@TepidDrink in your case, the pipeline of the With |
I was trying to use
What I'm trying to do is have the schema be valid only if The schema: const RegisterSchema = object(
{
email: string([
minLength(1, 'Please enter your email.'),
email('The email address is badly formatted.'),
]),
password1: string([
minLength(1, 'Please enter your password.'),
minLength(8, 'Your password must have 8 characters or more.'),
]),
privacy: literal(true, 'Must be true'),
password2: string(),
},
[
forward(
custom((input) => {
//THIS ONLY RUNS IF PRIVACY VALUE IS TRUE
return input.password1 === input.password2;
}, 'The two passwords do not match.'),
['password2']
),
]
); Stackblitz with a reproduction using a simple form in React. I hope this was proper etiquette in reporting, first time I'm ever writing an issue on a project |
You could change the boolean literal to |
Update: Previously, import * as v from 'valibot';
const RegisterSchema = v.pipe(
v.object({
email: v.pipe(
v.string(),
v.nonEmpty('Please enter your email.'),
v.email('The email address is badly formatted.'),
),
password1: v.pipe(
v.string(),
v.nonEmpty('Please enter your password.'),
v.minLength(8, 'Your password must have 8 characters or more.'),
),
password2: v.string(),
}),
v.forward(
v.partialCheck(
[['password1'], ['password2']],
(input) => input.password1 === input.password2,
'The two passwords do not match.',
),
['password2'],
),
); |
in my case password and confirm password have to be equal but I can't find which method I have to use
The text was updated successfully, but these errors were encountered: