T
TanStack4mo ago
robust-apricot

Better handle for conditional fields with error

Currently it's very cumbersome to use conditional fields, since there's no convenient way to handle errors on them. For example in 99% of the cases we want behavior like this: When all visible (mounted) fields are valid, despite some of the hidden fields have errors, it is considered a valid state. But currently the hidden field's error count as invalid state. Maybe changing the default behavior to ignore hidden field's error or adding option toFormOptions that determines if the form is valid/submittable or not might help?
16 Replies
vicious-gold
vicious-gold4mo ago
Can you make the hidden fields optional? I ran into something similar in my current project, and had to make the fields optional unless they were indeed supposed to be validated (for example, if selecting an option from another input made them visible)
robust-apricot
robust-apricotOP4mo ago
Can you elaborate on that? My current situation is like this: - There are some fields (Let's call it C.1 and C.2) that depends it's visibility on other fields (P) - There's error on C.1 - I changed P1 to other value so C.1 is hidden / C.2 is visible - I try to submit. There's no errors on visible fields (C.2 and P) - But there's hidden error state in C.1, so it's not in submittable state
fair-rose
fair-rose4mo ago
how did you define your validation?
robust-apricot
robust-apricotOP4mo ago
Here's basic reproduction
No description
robust-apricot
robust-apricotOP4mo ago
export function ExampleForm() {
const form = useForm({
defaultValues: {
p: true,
c: { a: '', b: '' },
},
});

return (
<form>
<form.Field name="p">
{(field) => <Checkbox checked={field.state.value} onCheckedChange={field.handleChange} />}
</form.Field>
<form.Subscribe selector={(state) => state.values.p}>
{(p) =>
p ? (
<form.Field name="c.a" validators={{ onChange: z.string().min(1, 'Should be at least 1 characters') }}>
{(field) => <TextField value={field.state.value} onValueChange={field.handleChange} />}
</form.Field>
) : (
<form.Field name="c.b">
{(field) => <TextField value={field.state.value} onValueChange={field.handleChange} />}
</form.Field>
)
}
</form.Subscribe>
</form>
);
}
export function ExampleForm() {
const form = useForm({
defaultValues: {
p: true,
c: { a: '', b: '' },
},
});

return (
<form>
<form.Field name="p">
{(field) => <Checkbox checked={field.state.value} onCheckedChange={field.handleChange} />}
</form.Field>
<form.Subscribe selector={(state) => state.values.p}>
{(p) =>
p ? (
<form.Field name="c.a" validators={{ onChange: z.string().min(1, 'Should be at least 1 characters') }}>
{(field) => <TextField value={field.state.value} onValueChange={field.handleChange} />}
</form.Field>
) : (
<form.Field name="c.b">
{(field) => <TextField value={field.state.value} onValueChange={field.handleChange} />}
</form.Field>
)
}
</form.Subscribe>
</form>
);
}
Also noticed even if there's no mounted field, the form.fieldInfo.instance is not cleaned up. Is there any programmatic way to determine if a field has any mounted instance? Related to the original problem - in the case where hidden fields are not expected to be submitted, it would be convenient if there's some sort of API that lets onSubmit callback to only return visible fields' values. Maybe marking if it should be ignored from validation/submission when it's unmounted on each field? The tricky part is that in most case we don't want to scrap the field's data completely - so the field state could be restored when it's mounted again.
fair-rose
fair-rose4mo ago
you can check with getFieldInfo().instance but if you want to programmatically delete fields, you can use deleteField but overall, if dynamic fields are a concern (and unmounted fields should be considered gone), then you should stick to form-level validation instead of field-level
robust-apricot
robust-apricotOP4mo ago
Strangely the instance is not cleared even when all fields are unmounted.
fair-rose
fair-rose4mo ago
:Hmm: that's strange. Can you create a reproducible example of it?
fair-rose
fair-rose4mo ago
thanks a lot! I‘ll check it out in a bit
robust-apricot
robust-apricotOP4mo ago
Yeah thank you as always for your great work ❤️ Also please consider above proposal or its alternatives... since RHF only accounts mounted fields' error for isValid
robust-apricot
robust-apricotOP2mo ago
Hi, is there anything going on for this topic? It's the same in the latest version I believe most users would expect unmount fields would clean up automatically
No description
fair-rose
fair-rose2mo ago
Hey! Sorry for the delay, it looks like this reproduction got lost. From what I can tell in the chatlog, there‘s no GitHub issue that was created for this, right? most users actually expect the opposite behaviour, where invalid fields that are unmounted aren‘t simply ignored. Still, this seems to be unrelated to isValid (which is stored in form state), but related to field instances not being cleaned.
robust-apricot
robust-apricotOP2mo ago
Hi Luca! https://stackblitz.com/edit/vitejs-vite-334l1e4w?file=src%2FApp.tsx I reuploaded the reproduction
From what I can tell in the chatlog, there‘s no GitHub issue that was created for this, right?
Yep, if this is a bug I can create it 👍
Still, this seems to be unrelated to isValid (which is stored in form state), but related to field instances not being cleaned.
You are right
most users actually expect the opposite behaviour, where invalid fields that are unmounted aren‘t simply ignored.
Yeah that makes sense... Then what about exposing the callback that would determine if the form is valid or not?
tjk
StackBlitz
Vitejs - Vite (duplicated) - StackBlitz
Next generation frontend tooling. It&#39;s fast!
fair-rose
fair-rose2mo ago
better yet, I think an onUnmount listener would make sense here in this case, deleting the field on the field‘s unmount. It would scrub the errors, instance and value
robust-apricot
robust-apricotOP2mo ago
in this case, deleting the field on the field‘s unmount. It would scrub the errors, instance and value
I think what you suggested and the isValid callback has different purpose - former preserves the field value and state but latter doesn't. Both makes sense in some situations. For example in a form with discriminated union segments user would lost all their inputs if they switch between them. Also - for the onUnmount cleanup, https://github.com/TanStack/form/pull/781 This seems like a related PR, before preserveValue option was removed I could achieve the wanted behavior with it but now I have to manually clean up

Did you find this page helpful?