Early codependent field validation

I’m making a signup form following the example here https://arktype.io/docs/expressions#narrow If you add another field, e.g. email, the narrow doesn’t run until email is valid. After reading other posts, I believe this is intended behavior. So then I went to try and add the .narrow into confirmPassword, but the type of ctx.root ends up being unknown, since we’re still validating the type, which makes sense. Since it’s unknown, this means having to do some really gross checks and type coercions to correctly check that password and confirmPassword don’t match. Wondering if there’s anything better I can do here.
1 Reply
Bobakanoosh
BobakanooshOP3d ago
Was hoping I could do something like this:
const PasswordSchema = type({
password: "string > 1",
confirm: "string > 1",
}).narrow((details, ctx) => {
if (details.password !== details.confirm) {
ctx.reject({
expected: "identical to password",
actual: "",
relativePath: ["confirm"],
})
}

return !ctx.hasError()
})

const FormArktype = type({
"...": PasswordSchema,
email: "string.email",
})
const PasswordSchema = type({
password: "string > 1",
confirm: "string > 1",
}).narrow((details, ctx) => {
if (details.password !== details.confirm) {
ctx.reject({
expected: "identical to password",
actual: "",
relativePath: ["confirm"],
})
}

return !ctx.hasError()
})

const FormArktype = type({
"...": PasswordSchema,
email: "string.email",
})
This works but not really ideal, especially in the case of a more complex schema
const PasswordSchema = type({
password: "string > 1",
confirm: "string > 1",
}).narrow((details, ctx) => {
if (details.password !== details.confirm) {
ctx.reject({
expected: "identical to password",
actual: "",
relativePath: ["confirm"],
})
}

return !ctx.hasError()
})

const FormArktype = type({
email: "string.email",
password: PasswordSchema,
}).pipe((v) => {
return {
...v,
...v.password,
}
})

const arkResult = FormArktype({ email: "[email protected]", password: { password: "2345", confirm: "2345" } })
if (arkResult instanceof type.errors) {
console.log(arkResult.summary)
} else {
console.log(arkResult)
}
const PasswordSchema = type({
password: "string > 1",
confirm: "string > 1",
}).narrow((details, ctx) => {
if (details.password !== details.confirm) {
ctx.reject({
expected: "identical to password",
actual: "",
relativePath: ["confirm"],
})
}

return !ctx.hasError()
})

const FormArktype = type({
email: "string.email",
password: PasswordSchema,
}).pipe((v) => {
return {
...v,
...v.password,
}
})

const arkResult = FormArktype({ email: "[email protected]", password: { password: "2345", confirm: "2345" } })
if (arkResult instanceof type.errors) {
console.log(arkResult.summary)
} else {
console.log(arkResult)
}
This is the aforementioned solution of narrowing directly on the dependent field. Probably the best solution I’ve got, though not ideal due to root being unknown and having to validate it myself.
const PasswordSchema = type({
password: "string > 1",
confirmPassword: type("string > 1").narrow((details, ctx) => {
const root = typeof ctx.root === "object" ? (ctx.root as Record<string, unknown>) : null
if (root && root.password !== root.confirmPassword) {
ctx.reject({
expected: "identical to password",
actual: "",
relativePath: ["confirmPassword"],
})
}

return !ctx.hasError()
}),
})

const FormArktype = type({
"...": PasswordSchema,
email: "string.email",
})

const arkResult = FormArktype({ email: "test", password: "2345", confirmPassword: "12" })
if (arkResult instanceof type.errors) {
console.log(arkResult.summary)
}
const PasswordSchema = type({
password: "string > 1",
confirmPassword: type("string > 1").narrow((details, ctx) => {
const root = typeof ctx.root === "object" ? (ctx.root as Record<string, unknown>) : null
if (root && root.password !== root.confirmPassword) {
ctx.reject({
expected: "identical to password",
actual: "",
relativePath: ["confirmPassword"],
})
}

return !ctx.hasError()
}),
})

const FormArktype = type({
"...": PasswordSchema,
email: "string.email",
})

const arkResult = FormArktype({ email: "test", password: "2345", confirmPassword: "12" })
if (arkResult instanceof type.errors) {
console.log(arkResult.summary)
}

Did you find this page helpful?