-
-
Notifications
You must be signed in to change notification settings - Fork 216
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
Conditionally requiring object fields #5
Comments
Thank you for creating this issue. Currently this is possible with our pipeline feature. Below is an example: import { object, number, enumType, custom, nullable } from 'valibot';
const YourSchema = object(
{
character: object({
type: enumType(['ninja', 'warrior', 'wizard']),
}),
shurikensAmount: nullable(number()),
},
[
custom(
(input) =>
input.character.type !== 'ninja' ||
typeof input.shurikensAmount === 'number',
'"shurikensAmount" is required if character type is "ninja"'
),
]
); If you need this pipeline validation more than once, you can offload it to a separate function, which makes the schema cleaner. import { object, number, enumType, nullable } from 'valibot';
import { checkShurikensAmount } from '~/validations';
const YourSchema = object(
{
character: object({
type: enumType(['ninja', 'warrior', 'wizard']),
}),
shurikensAmount: nullable(number()),
},
[checkShurikensAmount()]
); As soon as I have some more time, I will investigate if a special API can make this even easier and type-safe. |
Very cool 👍 thank you fabian! |
Hi, how can i do this on the field startsAt i want to be ensure the field startsAt is always lower as the field endsAt isBefore is just from date-fns export const createPollSchema = object(
{
question: string([minLength(3, 'Minimum 3 characters')]),
information: optional(string()),
id: optional(number()),
startsAt: coerce(date(), (input) => new Date(input as any)),
endsAt: coerce(date(), (input) => new Date(input as any)),
captcha: useDefault(boolean(), false),
pollOptions: array(pollOptions),
isClosed: useDefault(boolean(), false)
},
[
custom(
(input) => {
console.log(input);
return isBefore(input.startsAt, input.endsAt);
},
'startsAt is higher as endsAt'
)
]
); thanks |
@cannap thank you! If the if-condition is the right way around, your code looks good. I have tested the following code and it works. import { coerce, custom, date, object, parse } from 'valibot';
const forceDate = (input: any) => new Date(input);
export const Schema = object(
{
startsAt: coerce(date(), forceDate),
endsAt: coerce(date(), forceDate),
},
[
custom(
(input) => input.startsAt < input.endsAt,
'The date of "endsAt" and must be after "startsAt".'
),
]
);
parse(Schema, { startsAt: 0, endsAt: 1000 }); // { startsAt: Date; endsAt: Date }
parse(Schema, { startsAt: 1000, endsAt: 0 }); // Throws error |
yes but how can i add this function to the "startsAt" like: i just have access to the current value and not the whole object inside
//Pseude code
[
custom(
'startsAt', //would present the field where the custom validation is for or something
(input) => input.startsAt < input.endsAt,
'The date of "endsAt" and must be after "startsAt".'
),
] because i work with veevalidation/valibot //German |
I think that this is currently only possible through detours. However, I am aware of the problem and I will try to find a solution in the long run. For example, I could try to provide the entire input in a second parameter at The second option would be to add to Until then, you can write your own validation function that throws a import { coerce, date, object, parse, ValiError } from 'valibot';
const forceDate = (input: any) => new Date(input);
export const Schema = object(
{
startsAt: coerce(date(), forceDate),
endsAt: coerce(date(), forceDate),
},
[
(input) => {
if (input.startsAt > input.endsAt) {
throw new ValiError([
{
reason: 'date',
validation: 'custom',
origin: 'value',
message: 'Invalid date',
input: input.startsAt,
path: [
{
schema: 'object',
input: input,
key: 'startsAt',
value: input.startsAt,
},
],
},
]);
}
return input;
},
]
);
parse(Schema, { startsAt: 0, endsAt: 1000 }); // { startsAt: Date; endsAt: Date }
parse(Schema, { startsAt: 1000, endsAt: 0 }); // Throws error |
thanks! will try it |
Hi, maybe this is unrelated but when I'm adding a Currently I've something like this: const someStringList = ['a', 'b', 'c'];
export const vehicleUsageSchema = object(
{
controlField: withDefault(boolean(), false),
controlledField: nullish(string([custom((input) => someStringList.includes(input))]))
},
[
custom((input) => {
console.log(input);
return input.controlField? typeof input.controlledField === 'string' : true;
}, 'Some error message')
]
); |
You can use Thanks for the tip. Do you have time to investigate the problem or check with React Hook Form directly? My guess is that the problem is not with Valibot. |
Hey, @fabian-hiller thanks for the suggestion maybe I didn't see the |
Actually, this should not be the problem. However, more people have the problem: #76 (comment) |
I'll try researching a bit into this issue. Also, I don't think the |
You are right. Thank you! Can you share your schema that causes problems with React Hook Forms? |
Hi, @fabian-hiller I've created a StackBlitz repro for this bug: It's a form with 4 fields, of which 3 depend on the You can notice that every time the |
I have looked into it and can't figure out the problem. Can you create an issue at React Hook Form Resolvers? Feel free to link me. https://github.com/react-hook-form/resolvers |
So I went ahead and read:
And started tinkering with it. Turns out that replacing the So, I got my schema working as I wanted. It seems that To conclude, I've got a working example in this StackBlitz. The TL;DR would be that I just removed the P:S. @fabian-hiller, I wanted to know if you knew of a way in which I could improve this schema by making it easier to read. Thanks in advance! |
Hey, @fabian-hiller I was thinking of defining a custom pipe method like this: import { type Pipe, ValiError } from "valibot";
type WhenArguments<TValues> = {
dependsOn: keyof TValues;
field: keyof TValues;
constraint: (input: TValues) => boolean;
};
type PipeMethod = <TValues>(
args: WhenArguments<TValues>,
message?: string
) => Pipe<TValues>[number];
export const when: PipeMethod =
({ dependsOn, field, constraint }, message) =>
(input) => {
if (
input[dependsOn] &&
typeof input[field] === "string" &&
constraint(input)
) {
return { output: input };
}
let genericMessage = "";
if (!message) {
const dependsOnPretty = (dependsOn as string).replace(
/([a-z])([A-Z])/g,
"$1 $2"
);
const dependsOnLabel =
dependsOnPretty.charAt(0).toUpperCase() +
dependsOnPretty.substring(1, dependsOnPretty.length);
genericMessage = `This field is required when the ${dependsOnLabel} field is provided`;
}
throw new ValiError([
{
reason: "string",
validation: "custom",
origin: "value",
message: message ?? genericMessage,
input: input[field],
path: [
{
schema: "object",
input: input as Record<string, unknown>,
key: field as string,
value: input[field],
},
],
},
]);
}; I wanted to get your feedback on this or to know if could improve it a little bit, thanks in advance! |
Here's an example on StackBlitz with the prior idea ☝🏻 |
@demarchenac the internal implementation has changed with v0.13.0. Please read the release notes: https://github.com/fabian-hiller/valibot/releases/tag/v0.13.0 |
Oh ok, so I should use the |
@fabian-hiller ok I understand now. So, the |
Exactly. I will add the ability to specify the path soon. Thanks for your code examples regarding this. |
v0.15.0 is now available. |
I'll try it out when re-writing my |
@demarchenac feel free to let me know what you think about this. |
@fabian-hiller Yeah I was just reading that issue! |
I think this issue will be fixed in the next version (v0.31.0). If you think I am wrong or have a question, please ping me here. |
Hi! Thank you very much for this library. I do very appreciate the focus on performance and very high composability of rules! It's super awesome! Also congratulations of a first release!
I came here with this issue, because to this day I'm using
Zod
, since it's doing fine for me so far, but my biggest nitpick there is that there is no easy way to declare conditional requiring of the object properties. I wonder if there is a possibility to do that hereWhat do I mean specifically?
I mean that you can mark an object property as required (I'm assuming that's by default here), but only when given condition is met
How it could look like
f.e.
With data like this
parse()
should throw an error, becauseshurikensAmount
isnull
and it's required forcharacter.type === 'ninja'
but with data like this
should be fine, since wizards are not using shurikens
Conclusion
I'm not sure if this is a good idea or not. I'm only giving an idea for a solution for my problem where I struggled with trying to achieve this in Zod. I had to use
refine()
in this case, but it was more of a workaround rather than proper solution.link: colinhacks/zod#938 (comment)
Since this library focus on high composability of rules, I wonder if it there is a possibility to do that here.
If this is not a good idea, I will appreciate proper explanation
Have a good day!
The text was updated successfully, but these errors were encountered: