Unique attribute

Hi i created this schema to validate that a name is unique compared to existing name, however i wonder if it is possible to define the schema in a separate file and send the exiting names within the context Thank you for your help
const existingNames = ["a", "b", "c"]

const ItemSchema = type({
name: "string>0",
}).narrow((value, ctx) => {
if (existingNames.includes(value.name)) {
return ctx.reject({
expected: "unique",
actual: "",
path: ["name"],
});
}
return true;
});

type SchemaType = typeof ItemSchema.infer;

const methods = useForm<SchemaType>({
defaultValues: defaultValues ?? { name: "" },
resolver: arktypeResolver(ItemSchema),
shouldFocusError: false,
mode: "onChange",
});
const existingNames = ["a", "b", "c"]

const ItemSchema = type({
name: "string>0",
}).narrow((value, ctx) => {
if (existingNames.includes(value.name)) {
return ctx.reject({
expected: "unique",
actual: "",
path: ["name"],
});
}
return true;
});

type SchemaType = typeof ItemSchema.infer;

const methods = useForm<SchemaType>({
defaultValues: defaultValues ?? { name: "" },
resolver: arktypeResolver(ItemSchema),
shouldFocusError: false,
mode: "onChange",
});
7 Replies
TizzySaurus
TizzySaurus2mo ago
It's not possible to provide it in the context I guess one option would be to have existingNames as an exported let variable, which gets edited by other files
Moon shine
Moon shineOP2mo ago
but existing names here is actually names retreived from the database so i cannot export them
TizzySaurus
TizzySaurus2mo ago
// schemas.ts
export let existingNames: Set<string> = new Set(); // in reality this would need to fetch from the database when the program first runs

export const ItemSchema = type({....}).narrow(...);


// register.ts
import { existingNames, ItemSchema } from "./schemas";

const handleSignup = (event) => {
const { payload } = event;

const result = ItemSchema.assert(payload);

await insertUserIntoDatabase(payload);
existingNames.add(payload.name);
}
// schemas.ts
export let existingNames: Set<string> = new Set(); // in reality this would need to fetch from the database when the program first runs

export const ItemSchema = type({....}).narrow(...);


// register.ts
import { existingNames, ItemSchema } from "./schemas";

const handleSignup = (event) => {
const { payload } = event;

const result = ItemSchema.assert(payload);

await insertUserIntoDatabase(payload);
existingNames.add(payload.name);
}
In theory something like this should be fine You just have an in-memory cache of the names (which I didn't as a set rather than an array for optimisation) Otherwise you'd need to fetch the names within the .narrow(), but doing a database call on every otherwise validation seems nasty to me @Moon shine
Moon shine
Moon shineOP2mo ago
Thank you for your solution! but i think if exporting a Set of existingNames to be edited by other components can lead to race conditions or stale data and tests may interfere with each other since the Set isn’t isolated
TizzySaurus
TizzySaurus2mo ago
If you're in that scenario, you can look into solutions like Redis, which from my understanding are designed specifically to get around those issues
Moon shine
Moon shineOP2mo ago
Thank you for your response i will look into it Hi @TizzySaurus i ended up adding a useEffect that will setError manually, however it's overridden by the arktype schema so the i had to add a setTimeout. i just wonder would you do it differently ?
useEffect(() => {
if (!watchedName) return;

const timeout = setTimeout(() => {
if (existingNames.includes(watchedName.trim())) {
setError("name", { type: "manual", message: "Name must be unique" });
} else {
const currentNameError = methods.formState.errors.name;
if (currentNameError?.type === "manual") {
clearErrors("name");
}
}
}, 100);
return () => clearTimeout(timeout);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [watchedName, existingNames, setError, clearErrors]);
useEffect(() => {
if (!watchedName) return;

const timeout = setTimeout(() => {
if (existingNames.includes(watchedName.trim())) {
setError("name", { type: "manual", message: "Name must be unique" });
} else {
const currentNameError = methods.formState.errors.name;
if (currentNameError?.type === "manual") {
clearErrors("name");
}
}
}, 100);
return () => clearTimeout(timeout);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [watchedName, existingNames, setError, clearErrors]);
TizzySaurus
TizzySaurus2mo ago
Possibly https://discord.com/channels/957797212103016458/957804102685982740/1399438259641123038 It seems weird to me having additional validation outside the AT type

Did you find this page helpful?