T
TanStack3w ago
conscious-sapphire

Reset form after Submit not working as expected?

So, I have a React App and a simple form and I want to set the default values to whatever the form has at the time of submission. However, doing so resets the form to the original defaultValues, instead of the new ones passed to the function. Is there anything I'm doing wrong? Here's a snippet with this:
import { Button } from '@/components/ui/button';
import { useAppForm } from '@/hooks/form';
import { useStore } from '@tanstack/react-form';
import { z } from 'zod';

const formSchema2 = z.object({
gender: z.boolean(),
});

export function TestForm() {
const form = useAppForm({
defaultValues: {
gender: true,
},
validators: {
onChange: formSchema2,
},
onSubmit: async (params) => {
// params.value is correct here. If the toggle is false, it shows params.value.gender is false
console.log(params.value);

params.formApi.reset(params.value);
// after this line, the form goes back to the original defaultValues ({gender: true}) instead of the new one ({gender:false})
},
});

return (
<div>
<form
onSubmit={(e) => {
e.preventDefault();
form.handleSubmit();
}}
>
<form.AppForm>
<form.AppField
name='gender'
children={(field) => <field.ToggleField name={field.name} label='Show on Resume' className='ml-2' />}
/>
<Button type='button' onClick={() => form.handleSubmit()} disabled={!isDirty} className='mt-4'>
Submit
</Button>
</form.AppForm>
</form>
</div>
);
}
import { Button } from '@/components/ui/button';
import { useAppForm } from '@/hooks/form';
import { useStore } from '@tanstack/react-form';
import { z } from 'zod';

const formSchema2 = z.object({
gender: z.boolean(),
});

export function TestForm() {
const form = useAppForm({
defaultValues: {
gender: true,
},
validators: {
onChange: formSchema2,
},
onSubmit: async (params) => {
// params.value is correct here. If the toggle is false, it shows params.value.gender is false
console.log(params.value);

params.formApi.reset(params.value);
// after this line, the form goes back to the original defaultValues ({gender: true}) instead of the new one ({gender:false})
},
});

return (
<div>
<form
onSubmit={(e) => {
e.preventDefault();
form.handleSubmit();
}}
>
<form.AppForm>
<form.AppField
name='gender'
children={(field) => <field.ToggleField name={field.name} label='Show on Resume' className='ml-2' />}
/>
<Button type='button' onClick={() => form.handleSubmit()} disabled={!isDirty} className='mt-4'>
Submit
</Button>
</form.AppForm>
</form>
</div>
);
}
45 Replies
genetic-orange
genetic-orange3w ago
what version of tanstack form do you use?
conscious-sapphire
conscious-sapphireOP3w ago
@Luca | LeCarbonator was using 1.1.0, but now using 1.18.0. The result is still the same.
genetic-orange
genetic-orange3w ago
hmm, strange. Bad news, I see the same error you do. Good news, I can replicate it. I‘ll check what the issue may be and get back to you 👍 thanks for the report!
conscious-sapphire
conscious-sapphireOP3w ago
Thank you so much @Luca | LeCarbonator ! Appreciate your help!
genetic-orange
genetic-orange3w ago
can you confirm that it's truly not been fixed? I don't remember which minor version it was (somewhere between .10 and .17), but the reset in onSubmit has had a fix
genetic-orange
genetic-orange3w ago
LeCarbonator
StackBlitz
TSF + Zod Sandbox (duplicated) - StackBlitz
Next generation frontend tooling. It&#39;s fast!
conscious-sapphire
conscious-sapphireOP3w ago
Sure, let me try it out
genetic-orange
genetic-orange3w ago
this error was from my part because I had installed an alpha version from a previous test
conscious-sapphire
conscious-sapphireOP3w ago
oh I see. I did have a previous version installed in this project. and using pnpm as package manager. maybe something got mixed up?
genetic-orange
genetic-orange3w ago
maybe you tested after installing and it didn‘t reload properly
conscious-sapphire
conscious-sapphireOP3w ago
That could be true. And I've just tested it, but no luck Let me remove and reinstall the package Still nothing I also just reviewed my pnpm-lock.yaml and looks like the right version is referenced everywhere also, looks like the code for v 1.18.0 is the one in my node_modules
genetic-orange
genetic-orange3w ago
hmmm … can you spot a major difference between my stackblitz and your failing snippet?
conscious-sapphire
conscious-sapphireOP3w ago
I think one of the biggest differences is that I use "useAppForm" instead of plain "useForm", because I have a bunch of custom components Let me try your exact example in my project yep... that did it
genetic-orange
genetic-orange3w ago
that did it??
conscious-sapphire
conscious-sapphireOP3w ago
using "useForm" instead of using "useAppForm"
genetic-orange
genetic-orange3w ago
that … doesn‘t make any sense. But I‘ll try and confirm just in case
conscious-sapphire
conscious-sapphireOP3w ago
Just FYI, this is what I'm doing in order to generate the useAppForm hook (pretty much textbook example from the docs):
import { lazy } from 'react'
import { createFormHook } from '@tanstack/react-form'
import { fieldContext, formContext } from './form-context'

const TextField = lazy(() => import('../components/text-field.tsx'))
const NumberField = lazy(() => import('../components/number-field.tsx'))
const SelectField = lazy(() => import('../components/select-field.tsx'))
const DatePickerField = lazy(() => import('../components/date-picker-field.tsx'))
const RadioGroupField = lazy(() => import('../components/radio-group-field.tsx'))
const CheckboxField = lazy(() => import('../components/checkbox-field.tsx'))
const ToggleField = lazy(() => import('../components/toggle-field.tsx'))
const SubmitButton = lazy(() => import('../components/submit-button.tsx'))

export const { useAppForm, withForm } = createFormHook({
fieldContext,
formContext,
fieldComponents: {
TextField,
NumberField,
SelectField,
DatePickerField,
RadioGroupField,
CheckboxField,
ToggleField,
},
formComponents: {
SubmitButton,
},
})
import { lazy } from 'react'
import { createFormHook } from '@tanstack/react-form'
import { fieldContext, formContext } from './form-context'

const TextField = lazy(() => import('../components/text-field.tsx'))
const NumberField = lazy(() => import('../components/number-field.tsx'))
const SelectField = lazy(() => import('../components/select-field.tsx'))
const DatePickerField = lazy(() => import('../components/date-picker-field.tsx'))
const RadioGroupField = lazy(() => import('../components/radio-group-field.tsx'))
const CheckboxField = lazy(() => import('../components/checkbox-field.tsx'))
const ToggleField = lazy(() => import('../components/toggle-field.tsx'))
const SubmitButton = lazy(() => import('../components/submit-button.tsx'))

export const { useAppForm, withForm } = createFormHook({
fieldContext,
formContext,
fieldComponents: {
TextField,
NumberField,
SelectField,
DatePickerField,
RadioGroupField,
CheckboxField,
ToggleField,
},
formComponents: {
SubmitButton,
},
})
genetic-orange
genetic-orange3w ago
yeah, doesn't seem like a problem what happens if you add this snippet
<form.Subscribe selector={(state) => state.values}>
{(values) => <pre>{JSON.stringify(values, null, 3)}</pre>}
</form.Subscribe>
<form.Subscribe selector={(state) => state.values}>
{(values) => <pre>{JSON.stringify(values, null, 3)}</pre>}
</form.Subscribe>
see what the values are reactively. Do they also wrongly update?
conscious-sapphire
conscious-sapphireOP3w ago
yes they do
genetic-orange
genetic-orange3w ago
https://stackblitz.com/edit/vitejs-vite-jd2ttqqx?file=src%2FApp.tsx here's a version with useAppForm. Perhaps the field component is the key? because this appears to work the same
conscious-sapphire
conscious-sapphireOP3w ago
testing! I'm sorry to be such a troublemaker here, but the same thing just happened. Instead of passing the field.ToggleField component, I used the same plain html from your example and didn't work I'm reading yours line by line trying to figure out if there are other differences
genetic-orange
genetic-orange3w ago
well, there's of course the differences outside too. How are the defaultValues passed to the form etc.
conscious-sapphire
conscious-sapphireOP3w ago
ok wait... this is super weird I had the following code, and when removing it from the component, it started behaving
genetic-orange
genetic-orange3w ago
removing it from the component?
conscious-sapphire
conscious-sapphireOP3w ago
const [isDirty] = useStore(form.store, (state) => [state.isDirty]);
const [isDirty] = useStore(form.store, (state) => [state.isDirty]);
That's the code that I use to track whether or not the submit button should be enabled. when I remove it, the form's reset behaves
genetic-orange
genetic-orange3w ago
that sounds like it could be it what's likely happening is a race condition between two state changes: one change to set the new defaultValues, the other to set isSubmitting and isDirty etc. to their old values if the defaultValues is done first, then the isSubmitting and isDirty overwrites it accidentally should be easy to check though. Does this fix your implementation:
- params.formApi.reset(params.value)
+ setTimeout(params.formApi.reset(params.value), 50)
- params.formApi.reset(params.value)
+ setTimeout(params.formApi.reset(params.value), 50)
conscious-sapphire
conscious-sapphireOP3w ago
500 ms did it
genetic-orange
genetic-orange3w ago
well, removing an internal reload from isSubmitting was how the initial fix worked to think it was this easily recreatable
conscious-sapphire
conscious-sapphireOP3w ago
so it's like a zombie of the old issue?
genetic-orange
genetic-orange3w ago
yeah
conscious-sapphire
conscious-sapphireOP3w ago
I'm not familiar with tanstack's form code, but I took a peek and completely missed any sort of race condition
conscious-sapphire
conscious-sapphireOP3w ago
GitHub
form/packages/form-core/src/FormApi.ts at main · TanStack/form
🤖 Headless, performant, and type-safe form state management for TS/JS, React, Vue, Angular, Solid, and Lit. - TanStack/form
conscious-sapphire
conscious-sapphireOP3w ago
so, the interaction is between reset and handleSubmit I guess
genetic-orange
genetic-orange3w ago
yes, it‘s specifically if reset is called in onSubmit
conscious-sapphire
conscious-sapphireOP3w ago
ouch. and I would guess there's no other place to hook into and do the reset, like an "onAfterSubmit" (FML with that name)
genetic-orange
genetic-orange3w ago
no, but you ought not to since this is "only" needed if you use a store hook of a property that is affected by the end of submissions so I suppose the fix wasn't as simple as we initially thought. @nneaowwplane good luck :KEKPOINT: could you open a github issue for this? @Rodrigo
conscious-sapphire
conscious-sapphireOP3w ago
On it @Luca | LeCarbonator , and thank you a lot for your help on this one. @nneaowwplane I guess you're the one who's taking it on... oh boy I'm so sorry
genetic-orange
genetic-orange3w ago
he's the one who was forced to tackle the previous one :mhm: https://stackblitz.com/edit/vitejs-vite-jd2ttqqx?file=src%2FApp.tsx here's a reproduction for the issue
conscious-sapphire
conscious-sapphireOP3w ago
Thanks a bunch Luca
genetic-orange
genetic-orange3w ago
thanks for the report!
conscious-sapphire
conscious-sapphireOP3w ago
GitHub
form.reset during onSubmit ignores new default values · Issue #168...
Describe the bug The issue is that if you use the form&#39;s reset function from inside the form&#39;s onSubmit with new default values, they will be ignored. This only happens if used in combinati...
eager-peach
eager-peach3w ago
you don't suppose it's because the useStore is triggering a re-render?
genetic-orange
genetic-orange3w ago
that's the cause, yes. You can comment it out to 'fix' the effect
eager-peach
eager-peach3w ago
this is really wierd
conscious-sapphire
conscious-sapphireOP3w ago
Yes it is

Did you find this page helpful?