T
TanStack2w ago
other-emerald

form is populating with errors but errors.status logging as 'clean'

here's the form schema, it's somewhat complicated, but it's super strange to me that it'd be showing as clean even though there's error messages present
const createSchema = (total: number) => {
const FormNumber = S.transform(S.String, S.Number, {
decode: (s) => (s.trim() === "" ? 0 : parseFloat(s.trim())),
encode: (n) => (n === 0 ? "" : n.toFixed(2)),
});

const SplitsSchema = S.Array(
S.Struct({
memberUserId: S.UUID,
amount: FormNumber.pipe(
S.positive(local.annotate("Must contribute > 0%")),
S.lessThan(total, local.annotate("Can't contribute > 100%")),
),
memberName: S.Union(S.String, S.Literal("anon")),
}),
).pipe(
S.filter(
(splits) => {
const splitsSum = Number.sumAll(splits.map((s) => s.amount));

console.log(splitsSum, total, "\n\n");

return total === splitsSum;
},
{
message: () =>
Match.value(view).pipe(
Match.when("percent", () => "Splits must sum to 100%"),
Match.when("amount", () => `Splits must sum to $${total}`),
Match.orElseAbsurd,
),
},
),
);

const Schema = S.Struct({
description: S.String,
date: S.DateFromSelf,
amount: FormNumber.pipe(
S.int(local.annotate("Amount must be a whole number")),
S.positive(local.annotate("Item must cost at least $0.01")),
S.lessThan(100_000, local.annotate("Item must not exceed $100,000")),
),
paidBy: S.Literal(...participants).pipe(
S.annotations(local.annotate("Paid By must be a valid member")),
),
splits: SplitsSchema,
});

return Schema;
};
const createSchema = (total: number) => {
const FormNumber = S.transform(S.String, S.Number, {
decode: (s) => (s.trim() === "" ? 0 : parseFloat(s.trim())),
encode: (n) => (n === 0 ? "" : n.toFixed(2)),
});

const SplitsSchema = S.Array(
S.Struct({
memberUserId: S.UUID,
amount: FormNumber.pipe(
S.positive(local.annotate("Must contribute > 0%")),
S.lessThan(total, local.annotate("Can't contribute > 100%")),
),
memberName: S.Union(S.String, S.Literal("anon")),
}),
).pipe(
S.filter(
(splits) => {
const splitsSum = Number.sumAll(splits.map((s) => s.amount));

console.log(splitsSum, total, "\n\n");

return total === splitsSum;
},
{
message: () =>
Match.value(view).pipe(
Match.when("percent", () => "Splits must sum to 100%"),
Match.when("amount", () => `Splits must sum to $${total}`),
Match.orElseAbsurd,
),
},
),
);

const Schema = S.Struct({
description: S.String,
date: S.DateFromSelf,
amount: FormNumber.pipe(
S.int(local.annotate("Amount must be a whole number")),
S.positive(local.annotate("Item must cost at least $0.01")),
S.lessThan(100_000, local.annotate("Item must not exceed $100,000")),
),
paidBy: S.Literal(...participants).pipe(
S.annotations(local.annotate("Paid By must be a valid member")),
),
splits: SplitsSchema,
});

return Schema;
};
11 Replies
other-emerald
other-emeraldOP2w ago
when i set amount to any value that's not sum(splits.amount), i should be getting an error on the splits.amount checks i even do, i can see in the logs, i just get more or less { status: 'clean', message: '...' }
robust-apricot
robust-apricot2w ago
what is this status state you're referring to? How is this schema implemented in the form? tanstack form doesn't check for status: "clean" or the like, it checks for truthy values. This means that returning successful parses will be considered an error
other-emerald
other-emeraldOP2w ago
I believe it’s just on the standard schema v1 type? I was dealing with this property in valibot and now in effect schema In valibot it seemed to reflect if it was actually errored in the form, not the case in effect schema right now
robust-apricot
robust-apricot2w ago
hmmm … so the code above is effect schema? I‘ll tinker with it on my end later this evening, see if I can replicate it
other-emerald
other-emeraldOP2w ago
Yea it’s effect schema
robust-apricot
robust-apricot2w ago
looks like effect schema expects some parsing if you want to use it like a standard schema. Did you make sure to do that before using it?
robust-apricot
robust-apricot2w ago
other-emerald
other-emeraldOP2w ago
Yea I am, there's a type mismatch when passing to the form instance if you don't convert it
validators: {
onChange: (ctx) => {
const total = parseFloatCustom(ctx.value.amount);
const Schema = createSchema(total);
const Standard = S.standardSchemaV1(Schema);

return ctx.formApi.parseValuesWithSchema(Standard);
},
validators: {
onChange: (ctx) => {
const total = parseFloatCustom(ctx.value.amount);
const Schema = createSchema(total);
const Standard = S.standardSchemaV1(Schema);

return ctx.formApi.parseValuesWithSchema(Standard);
},
robust-apricot
robust-apricot2w ago
can you share the logged value of ctx.formApi.parseValuesWithSchema(Standard)?
other-emerald
other-emeraldOP2w ago
No description
other-emerald
other-emeraldOP2w ago
this is how i'm getting the errors to render in the component, something about the metas?
export const metasToErrors = (metasRecord: Record<any, AnyFieldMeta>) => {
const metas = Object.values(metasRecord);

const errors = metas
.map((meta) => meta.errors as StandardSchemaV1Issue[])
.flat();

const hasErrors = metas.some((meta) => meta.isTouched) && errors.length > 0;

return {
status: hasErrors ? "errored" : "clean",
values: errors,
} as const;
};
export const metasToErrors = (metasRecord: Record<any, AnyFieldMeta>) => {
const metas = Object.values(metasRecord);

const errors = metas
.map((meta) => meta.errors as StandardSchemaV1Issue[])
.flat();

const hasErrors = metas.some((meta) => meta.isTouched) && errors.length > 0;

return {
status: hasErrors ? "errored" : "clean",
values: errors,
} as const;
};

Did you find this page helpful?