Yup is a super popular object validation library written in TypeScript.
This post will assume a basic working knowledge of Yup.
Not only does Yup provide a large array of built-in validation functions, but it also allows users to customize tests to suit their needs. These custom test functions accept an options
object, which allows for external values to be passed in to be used in writing the boolean logic within the test.
On the documentation page, they give this example:
const jamesSchema = string().test({
name: 'is-james',
message: (d) => `${d.path} is not James`,
test: (value) => value == null || value === 'James',
}
);
jamesSchema.validateSync('James'); // "James"
jamesSchema.validateSync('Jane'); // ValidationError "this is not James"
But what if the name to be tested is not "James"? What if we want to pass in a name against which to validate? Well, the validate
and validateAt
functions, as well as their sync
alternatives accept an options
object, which can include external values. The options
object is included as part of the context
object passed to the body of the test function. More info here.
If we adjust the previous example, we can pull the name out of the context options object within the test function.
const nameSchema= string().test({
name: 'is-name',
message: (d) => `${d.path} is not James`,
test: (value, context) => (
value !== null &&
value === context.options.targetName
),
}
);
This will throw a ValidationError
with the message this is not James
. However, the name "James" is hardcoded into the error message and has no relation to the target value stored in context.options.targetName
.
If we log the value of context
within the test function, it looks like this:
{
"path": "",
"type": "is-name",
"options": {
"targetName": "Ringo",
"sync": true
},
"originalValue": "James",
"schema": {
// ...excluded for brevity
}
}
As we can see, the targetName
property is included in the options
object within the context
object.
However, if we log the object that's passed to the message
function, we see something different.
{
"value": "James",
"originalValue": "James",
"path": "this",
"spec": {
"strip": false,
"strict": false,
"abortEarly": true,
"recursive": true,
"nullable": false,
"optional": true,
"coerce": true
}
}
Uh-oh, the options
object isn't included in the object passed to the message
function. So, what do we do if we need to pass a value through the context object to be used in the error message?
Well, the solution is to forego use of the message function entirely. The context object in the test function has a createError
function which can be used to format a Yup ValidationError
message. The result of this function can be returned if the resulting boolean from the test function is false and because it is in the scope of the test function, it still has access to the context values passed to the function.
const nameSchema = string().test({
name: 'is-name',
test: (value, context) => (
value !== null &&
value === context.options.targetName
) || context.createError(`${context.path} is not ${context.options.targetName}`),
}
);
Now the target name value will be formatted into the resulting ValidationError
message.
Here's a working example.
It would be great if the context object would be passed to the message function as well. It would simplify the error-handling logic and isolate it from the test logic. There is an open issue on Yup's Github page about this, so hopefully it'll get resolved in an upcoming update release.
Top comments (0)