T
TanStack4mo ago
conscious-sapphire

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
conscious-sapphire
conscious-sapphireOP4mo 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: '...' }
national-gold
national-gold4mo 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
conscious-sapphire
conscious-sapphireOP4mo 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
national-gold
national-gold4mo 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
conscious-sapphire
conscious-sapphireOP4mo ago
Yea it’s effect schema
national-gold
national-gold4mo 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?
national-gold
national-gold4mo ago
conscious-sapphire
conscious-sapphireOP4mo 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);
},
national-gold
national-gold4mo ago
can you share the logged value of ctx.formApi.parseValuesWithSchema(Standard)?
conscious-sapphire
conscious-sapphireOP4mo ago
No description
conscious-sapphire
conscious-sapphireOP4mo 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?