DEV Community

Sushil
Sushil

Posted on

reset() Inside onSubmit Only Half-Resets a React Hook Form. Here's What Stays Behind

You save a form, call reset() in your submit handler, and the inputs go blank. Looks done. Then the user starts typing the next entry and a red "Title needs 3+ chars" shows up while they're still typing, on a form that's supposed to be fresh.

The values were reset. The submission state wasn't.

Why it happens

The React Hook Form reset docs spell this out in their rules: "Avoid calling reset inside onSubmit. handleSubmit updates isSubmitted, isSubmitSuccessful and submitCount after your handler finishes, so a reset there clears the values but not the submission state."

So the order is: your handler runs, reset() clears everything, then handleSubmit finishes and writes "submitted, successful, count +1" back on top. And once isSubmitted is true, the default reValidateMode: 'onChange' kicks in, which is why errors start firing while the user types.

What I ran

I wanted to see the exact state, so I rendered a one-field form in jsdom with Testing Library and drove it with user-event: React 19.3.1, react-hook-form 7.91.0, zod 4.6.7, @hookform/resolvers 7.0.2, Node 20.19.2. The schema is z.object({ title: z.string().min(3) }). I ran three versions: reset() inside a sync onSubmit, inside an async one (awaits a 20 ms timer first), and the useEffect version from the docs.

Here's the state right after a successful save, and after typing "ab" into the now-empty field without submitting:

After save Error after typing "ab"
reset() in sync onSubmit isSubmitted: true, isSubmitSuccessful: true, submitCount: 1 "Title needs 3+ chars"
reset() in async onSubmit isSubmitted: true, isSubmitSuccessful: true, submitCount: 1 "Title needs 3+ chars"
reset() in useEffect isSubmitted: false, isSubmitSuccessful: false, submitCount: 0 none

Async made no difference. And submitCount never really climbs with the in-handler version: after the second save it was back at 1, because reset() zeroes it and handleSubmit adds one. If you use submitCount for anything, like "show the hint after two failed tries", that's quietly wrong.

One thing I couldn't reproduce: errors getting stuck. In every version, a too-short title showed the error and fixing it cleared it. What changes is when validation starts, not whether it works.

The fix

Move the reset into an effect that watches isSubmitSuccessful. This is the docs' "Submit with Reset" pattern, with the schema from my test:

import { useEffect } from 'react';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';

const schema = z.object({ title: z.string().min(3, 'Title needs 3+ chars') });
type FormValues = z.infer<typeof schema>;

export function TaskForm({ save }: { save: (v: FormValues) => Promise<void> }) {
  const {
    register,
    handleSubmit,
    reset,
    formState: { errors, isSubmitSuccessful },
  } = useForm<FormValues>({
    resolver: zodResolver(schema),
    defaultValues: { title: '' },
  });

  useEffect(() => {
    if (isSubmitSuccessful) reset({ title: '' });
  }, [isSubmitSuccessful, reset]);

  return (
    <form onSubmit={handleSubmit(save)}>
      <input aria-label="title" {...register('title')} />
      {errors.title && <p>{errors.title.message}</p>}
      <button type="submit">Save</button>
    </form>
  );
}
Enter fullscreen mode Exit fullscreen mode

reset() clears isSubmitSuccessful too, so the effect fires again on every later save, not just the first one. In my run it did: after the second save the state was back to false/false/0.

If save throws, nothing resets, which is usually what you want: in my run the field kept "kept text" after a failing save. handleSubmit passes the error on, though, and here it ended up as an unhandled promise rejection, so catch it inside save and show the user something.

I type-checked that exact component with TypeScript 5.9.3 (strict) and ran it through the same jsdom setup: the field cleared after each save and typing "ab" afterwards showed no error. All of this ran in jsdom, not a real browser. The behavior comes from React Hook Form's state handling rather than the DOM, so I'd expect the same in an app, but I haven't checked that part.

If you've got reset() sitting at the bottom of a submit handler somewhere, it's a two-line move. Worth it just to get submitCount honest again.

Top comments (1)

Collapse
 
rushdeploy profile image
Rishabh Mishra •

This is a clean catch, and the submitCount detail is the part that would bite most teams. Moving reset into the isSubmitSuccessful effect keeps the form state honest after a save.