T
TanStack•3mo ago
absent-sapphire

How to async load defaultValues from a global state?

I should load my defaultValues for useAppForm from a global store. However, i dont want to block the rendering of the form with a loading or a spinner, so i am doing this:
useEffect(() => {
form.setFieldValue('delivery_method', delivery_method)
form.setFieldValue('recipient_name', recipient_name)
form.validate('change')
}, [delivery_method, recipient_name])
useEffect(() => {
form.setFieldValue('delivery_method', delivery_method)
form.setFieldValue('recipient_name', recipient_name)
form.validate('change')
}, [delivery_method, recipient_name])
This works (even though i don't know if the recommended approach). However, due to this form.validate(), all non-optional fields with null values shows the error, even tough error content has been guarded by the "field.state.meta.isTouched && !field.state.meta.isValid". Is this an intended approach? Apparently setFieldValue marks fields touched by default. Ps: i know setFieldValue has a dontUpdateMeta opt prop, but this seems to not only not marking isTouched: true but also would make form.validate('change') to have no effect. This seems a much trivial usecase but I am might have not yet found an elegant way to handle it. Appreciate if anyone can shine some light! 🙂
23 Replies
xenial-black
xenial-black•3mo ago
Have you read through the async initial values section of the documentation? https://tanstack.com/form/latest/docs/framework/react/guides/async-initial-values
Async Initial Values | TanStack Form React Docs
Let's say that you want to fetch some data from an API and use it as the initial value of a form. While this problem sounds simple on the surface, there are hidden complexities you might not have thou...
xenial-black
xenial-black•3mo ago
if so, what were the issues with implementing it this way?
absent-sapphire
absent-sapphireOP•3mo ago
Thanks for you response @Luca | LeCarbonator Yes. I have read it all a couple of times. If you see the documentation, you will see that it loads the initial data from external using Ts query, in which can greatly control is loading state. Example is not loading from a global state. In my case, I have a multi step form which makes no sense to load data on every step so I am fetching once and setting on a state. I also don’t want to block the form rendering because in terms of UI I think makes no sense. So here is the thing: how to asynchronous load from a state, for example while not blocking form rendering. I spent a few hours but honestly haven’t found a proper solution as I said. Apparently form.setfieldvalue + form.validate should be the answer but these makes all fields touched which is not the case. I want to silently load the data. Any thoughts @Luca | LeCarbonator ?
xenial-black
xenial-black•3mo ago
well, do delivery_method and recipient_name cause a rerender? They should be able to be passed the same as in the documentation example
absent-sapphire
absent-sapphireOP•3mo ago
They would do yes! But this comes to another problem. How to validate it? Since validate, onMount would have already been validated with initial data which was undefined? Now you may notice where I am getting to.. right?
xenial-black
xenial-black•3mo ago
I'll see if I can create a stackblitz reproduction for experimenting
absent-sapphire
absent-sapphireOP•3mo ago
That would be great. I must say I never lazy at all 🙂 Once I post it is actually I have not found a proper solution for real 🙂 for sure I might have oversight some obvious solution, but that would be undocumented. If you need any help call me anytime! Would be happy to help!
xenial-black
xenial-black•3mo ago
Async mounted validation is rather rare, so I can't think of a cleaner solution than the programmatic approach you provided. https://stackblitz.com/edit/vitejs-vite-hrarbsuz?file=src%2FApp.tsx I've created a basic example of async state. From what I can tell, it works as expected. I used the guard you provided (field.state.meta.isTouched && !field.state.meta.isValid) and email does not show its error until it is changed. Could you take a look at the reproduction? Perhaps I misunderstood your desired behaviour of showing errors. If your desired behaviour is delivery_method and recipient_name to immediately show errors while other fields are unchanged, dontUpdateMetaneeds to be false (default). This will touch the fields it changed. If your desired behaviour is delivery_method and recipient_name to not show errors immediately unless they're changed, then dontUpdateMeta is the way to go. The errors are still generated, but are blocked from the user (see console)
absent-sapphire
absent-sapphireOP•3mo ago
@Luca | LeCarbonator you actually were mislead by default behaviour. Will explain... See your updated example: https://stackblitz.com/edit/vitejs-vite-hnfl3pzc?file=src%2FApp.tsx Its self explanatory, but check comments.
Matheus
StackBlitz
Asynchronous defaultValues with validation (duplicated) - StackBlitz
Next generation frontend tooling. It's fast!
xenial-black
xenial-black•3mo ago
I see now! The solution depends on what your submit button behaviour should be: 1. Async data is loaded, it is correct. The user did not change values yet. Should the submit button be enabled or disabled? 2. Async data is loaded, it is correct. The user changed other values. Should the submit button be enabled or disabled? 3. Async data is loaded, it has errors. Should they immediately show up? One property that can help with this is state.isPristine. It informs you whether or not there are changed values in your form. Alternatively, there is state.isDefaultValue in case you want to check for non-persistent dirty (like in react-hook-form) onMount validation makes little sense here as your values are asynchronous. Feel free to remove it.
If we use onMount validaiton, then form load as canSubmit = false, because all initial values are undefined.
Correct, so it's not useful for this form.
If we dont use onMount, then form load as canSubmit = true, even though it has not all values set
isPristine and isDefaultValue are the key to this. It depends on what your desired behaviour is though, so I changed the values to give visual feedback. Some notes: * canSubmit is based on fields and their isValid state, which means it's affected by dontUpdateMeta * isPristine is based on field meta, which means it's affected by dontUpdateMeta * isDefaultValue is a derived value determined from field.state.value and defaultValues, so it's always up to date regardless of dontUpdateMeta https://stackblitz.com/edit/vitejs-vite-hixhvdfg?file=src%2FApp.tsx
LeCarbonator
StackBlitz
Asynchronous defaultValues with validation (duplicated) - StackBlitz
Next generation frontend tooling. It's fast!
xenial-black
xenial-black•3mo ago
The async data was correct
No description
xenial-black
xenial-black•3mo ago
The async data is incorrect. Note how the error is immediately visible in the last one since isTouched: true
No description
xenial-black
xenial-black•3mo ago
As an unconventional approach, this may also be worth looking into:
useEffect(() => {
if (user) {
// reset form and field meta, but set defaultValues to the ones specified.
// this bypasses most manual value setting and avoiding meta updates.

// It will NOT rerun onMount or onChange validators.
form.reset(user);
}
}, [user]);
useEffect(() => {
if (user) {
// reset form and field meta, but set defaultValues to the ones specified.
// this bypasses most manual value setting and avoiding meta updates.

// It will NOT rerun onMount or onChange validators.
form.reset(user);
}
}, [user]);
xenial-black
xenial-black•3mo ago
No description
absent-sapphire
absent-sapphireOP•3mo ago
@Luca | LeCarbonator Thanks. Confess my head is spinning around on this. The UI decisions i have made are the simplest and straight forward as possible: - Async data is loaded, values are valid? Submit button enabled - Any value invalid? Submit button disabled. Is that simple as that - This is a very common usecase in the company forms. I should not show any errors because most likely the values (when loaded) will be valid. So user will rightly know that button is disabled because of missing values. In terms of UI is not correct to print errors to the user since user has not even touched any field. So about your examples: from what i understood, i should also only show render errors then isPristine is false, right? But this is what makes it very confusing, because, lets consider when async data is incorrect example: - DontUpdateMeta: true, canSubmit is true (?) while isPristine is true. I cannot rely on these to disable my submit button. - DontUpdateMeta: false, canSubmit is false (correct), but isPristine is also false (so i cannot rely on this either). I hope my usecase makes sense to you, but even considering isPristine and isDefaultValue, my brain is burning. Am I not seing something here?
xenial-black
xenial-black•3mo ago
Note There appears to be an issue with stale canSubmit. I somehow managed to end up in a state where errors is not empty, but canSubmitis true. I'll investigate further and create a GitHub issue for it. It appears to be related to calling change validation on pristine forms. Note that isValid appears to be unaffected. The requirements are: * Async data must be loaded (!!user) * The user can submit without touching fields (!isPristine || isPristine => true) * The form renders immediately, async data is set later * Validation must occur as soon as data is loaded * The form must be valid (isValid) * The form must be submittable (canSubmit) There appears to be an error with that as of v1.12.3. I'll investigate further. We can conclude that the correct check for the submitButton is therefore disabled={!user || !isValid} To summarize: * when data is loaded, use setFieldValue. We don't want to mark the field as touched, so use dontUpdateMeta: true * after setting the new data, trigger on change validation. form.validate('change'). See the example here: https://stackblitz.com/edit/vitejs-vite-hixhvdfg?file=src%2FApp.tsx I'm sorry that you had to encounter the bug like this. That was quite the time waster, but at least it's a known bug now. As far as user experience is concerned, if the initial values take time to load, they will overwrite any values that the user wrote, so keep that in mind
absent-sapphire
absent-sapphireOP•3mo ago
Thanks so much @Luca | LeCarbonator ! So at the end canSubmit was unintendedly being marked as true even though it had errors. Nice catch! I will use isValid for a while which seems to solve the issue. Thanks once again!
xenial-black
xenial-black•3mo ago
thanks for the example! It should narrow down the cause. I'll link the issue here once it's created. the difference between canSubmit and isValid is mostly with how they manage onMount errors and the isSubmittingstate, so it should not matter for this use case
fascinating-indigo
fascinating-indigo•3mo ago
xenial-black
xenial-black•3mo ago
based on the ssr example, it‘s probably because isSubmitting checks for the execution of onSubmit which is essentially immediate but I‘ve heard multiple times that it‘s unintuitive for nextjs. Could you list the expected behaviour and I‘ll see if it‘s possible to implement it?
fascinating-indigo
fascinating-indigo•3mo ago
I would like to see that isSubmitting:true during submit execution even in example i should see ... when is Submitting but it doesnt happen
fascinating-indigo
fascinating-indigo•3mo ago
StackBlitz
Form Next Server Actions Example - StackBlitz
Run official live example code for Form Next Server Actions, created by Tanstack on StackBlitz
fascinating-indigo
fascinating-indigo•3mo ago
it still not working i would like to use isSubmitting but seems I have to use isPending from useActionState

Did you find this page helpful?