T
TanStack6mo ago
wise-white

Updating field meta errors from server validation

Following the Tanstack Start example - I am trying to update field meta errors from server validation. The state coming from the form is merged - and the errors and the error map shows correctly in the form state. However, the errors are not trickled down to field level. I tried to use setFieldMeta from form level but the errors do not make it to the field. Here is the basic setup:
useEffect(() => {

if (state.errors.length > 0) {
Object.keys(taskFormOptions.defaultValues).forEach((fieldName) => {
const error = state.errors.find(
(field) => Object.keys(field)?.[0] === fieldName
);

if (error) {
const errorMsg = error?.[fieldName];
form.setFieldMeta(fieldName, (prev) => ({
...prev,
errors: [errorMsg], //<--- update error array
}));
}
});
}
}, [form, state.errors, state.errors.length]);
useEffect(() => {

if (state.errors.length > 0) {
Object.keys(taskFormOptions.defaultValues).forEach((fieldName) => {
const error = state.errors.find(
(field) => Object.keys(field)?.[0] === fieldName
);

if (error) {
const errorMsg = error?.[fieldName];
form.setFieldMeta(fieldName, (prev) => ({
...prev,
errors: [errorMsg], //<--- update error array
}));
}
});
}
}, [form, state.errors, state.errors.length]);
and then down the form ...
<form.AppField
name="budget"
children={(field) => (
<field.MoneyField
label="Budget ($)"
placeholder={"Enter the Budget in Dollars"}
useFieldContext={useTaskFieldContext}
/>
)}
/>
<form.AppField
name="budget"
children={(field) => (
<field.MoneyField
label="Budget ($)"
placeholder={"Enter the Budget in Dollars"}
useFieldContext={useTaskFieldContext}
/>
)}
/>
In the MoneyField component - I look for field state and even use useStore to look for field errors but the array stays empty. Ideally - I thought merging the state from route with form - would update all form and field states but its not happening. Anyways - if someone can give me a hint here what I am doing wrong - I would really appreciate it. Please let me know if you need to see more code.
9 Replies
magic-beige
magic-beige6mo ago
Have you tried using a validator, such as onSubmitAsync? They simply use their return value to reflect valid or error. You shouldn't have to set the validation state manually - plus, I would suspect that this manually set error state could easily get overridden the next time a validation runs.
wise-white
wise-whiteOP5mo ago
@shoff I saw that and its pretty nice; however, I don't think it is an equivalent replacement. I think we also need to evaluate the whole form after submit on the server. And I wanted to do it the Start way - which I think of lifecycles - where I do something - I invalidate the route > do something else ... etc. In my opinion the onServer errors have to trickle into the fields just like the other errors. Or at least be able to set them manually like above.
magic-beige
magic-beige5mo ago
Hmm, I'm not really familiar with start. But I do think that even if you somehow set the validation state without using validators, it would likely just get overridden the next time the validators are used, which don't give the error. For validation after submit, most people currently use the onSubmitAsync validator, do their submit inside of that and return undefined or the error depending on the result. With how often this has come up, I do wonder if we could allow the normal onSubmit() to act like a validator or add some kind of afterSubmit() validator that gets onSubmits return value as a prop. Or at otherwise add the current onSubmitAsync "pattern" to the docs. I have yet to see anyone successfully set form errors outside of the validators, and don't think it aligns well with how validation is supposed to work. At least for me, keeping everything that controls validation state (except resets) inside of the validators makes a lot more sense.
magic-beige
magic-beige5mo ago
What you can also do is trigger validation with validateAllFields: https://tanstack.com/form/latest/docs/reference/classes/formapi#validateallfields And then I suppose you could set something outside that a validator uses before form.validateAllFields()?
wise-white
wise-whiteOP5mo ago
@shoff Note that I was following the Start example in the documentation: https://tanstack.com/form/latest/docs/framework/react/guides/ssr
magic-beige
magic-beige5mo ago
And it doesn't work that way? I haven't tried start myself yet, and since the stackblitz example seems to be broken right now I have to make assumptions: In the guide code, it looks as if the errors are somehow already ending up on state.errors. Maybe as a byproduct of the form action bit or through the transform? If so,
const formErrors = useStore(form.store, (formState) => formState.errors)
const formErrors = useStore(form.store, (formState) => formState.errors)
is simply there to make them accessible for render. But maybe to ask differently: Where does your state.errors come from?
wise-white
wise-whiteOP5mo ago
In the Start example, once you submit the form > you get the correct state with error returned from loader . The state gets passed as a prop to the form > then to the useForm hook > where it gets merged with current state of the form. So far so good. So now lets say you have a <form.AppField name="error-prone-field" ... > and the state coming in from the route loader is gonna have example: { errorMap: { onServer: ["error-prone-field:" - "boom it happened"] }}} Now, inside the form.AppField component, i.e.: <field.TextFiled ... > we are going to get the field state from something like this const field = useFieldContext() ;The field state gets automatically updated for any other error - but not for onServer errors.
Perhaps, there are good reasons - but I think the field state should be either updated automatically - or the meta error be set from the form (by the way the api exists but doesnt work) - or some other way. Maybe the devs are not done with it yet. Not sure
magic-amber
magic-amber5mo ago
I cannot make it working with next react 19. No matter what the state from the server is not merged in the form state
wise-white
wise-whiteOP5mo ago
Invalidate the route once - and you should get the right state I use a mutation - not a direct call - so on error - router.invalidate().

Did you find this page helpful?