DEV Community

Shivani R
Shivani R

Posted on

Why Formik `isValid` Doesn’t Update Correctly in Multi-Step Forms (And the Fix That Worked for Me)

While building a multi-step form using React + Formik, I ran into a frustrating issue:

👉 The Next button stayed enabled even after moving to a new step with empty required fields.

At first, everything looked correct:

  • Separate validation schema per step
  • isValid used to disable navigation
  • Dynamic step rendering

But Formik validation state wasn’t updating properly when the step changed.

Here’s what was happening — and the solution that finally worked.


🧠 The Problem

I had a setup like this:

<Formik
  initialValues={initialValues}
  validationSchema={multiPageFormsValidationSchema[currentStep]}
  onSubmit={handleSubmit}
>
  // form logic
</Formik>
Enter fullscreen mode Exit fullscreen mode

Each step had its own validation schema.

The issue:

  • Step 1 becomes valid → isValid = true
  • Move to Step 2
  • New fields are empty
  • isValid still remains true

This caused the Next button to stay enabled incorrectly. But it would get disabled if its clicked with empty fields.


⚠️ Why This Happens

Formik does not automatically re-run validation properly when:

  • the validation schema changes dynamically
  • the form step changes
  • fields are conditionally rendered

So even though the schema changes:

validationSchema={multiPageFormsValidationSchema[currentStep]}
Enter fullscreen mode Exit fullscreen mode

Formik continues using stale validation state until another user interaction happens.


🔥 The Solution That Worked for Me

What finally fixed it was:

const formik = useFormik({
   initialValues,
   onSubmit: (values) => onSubmit(values),
   validationSchema: multiPageFormsValidationSchema[currentStep],
})
Enter fullscreen mode Exit fullscreen mode
const { values, resetForm, validateForm, handleSubmit, isValid } = formik;
Enter fullscreen mode Exit fullscreen mode
useEffect(() => {
  const checkFormValidation = async () => {
    resetForm({ values });
    await validateForm();
  };

  checkFormValidation();
}, [currentStep]);
Enter fullscreen mode Exit fullscreen mode
<FormikProvider value={formik}>
  <Form onSubmit={handleSubmit}>
    // form logic
  </Form>
</FormikProvider>
Enter fullscreen mode Exit fullscreen mode

✅ Why This Works

resetForm({ values })

This forces Formik to:

  • reset Formik’s internal state while preserving the current form values
  • reinitialize touched/errors state correctly
  • acknowledge the new schema properly

Without this, Formik sometimes keeps stale validation metadata from the previous step.


validateForm()

This immediately re-runs validation for the current step schema.

So when the step changes:

  • new fields get validated instantly
  • isValid updates correctly
  • navigation buttons behave properly

Why I Used useFormik + FormikProvider Instead of the Component

This gave me more control over the form instance and validation lifecycle.

Since my form behavior depended heavily on:

  • dynamic step changes
  • manually triggering validation
  • resetting form state
  • accessing Formik methods inside useEffect

Using useFormik made the logic easier to manage. It also allowed me to directly access methods like:

resetForm();
validateForm();

without relying on render props or hooks inside nested components.

For multi-step forms with dynamic validation flows, this pattern felt much cleaner and more flexible than the standard wrapper.


🧪 Final Result

Now when the user changes steps:

  • validation updates immediately
  • isValid reflects current step state
  • buttons enable/disable correctly

🤔 Other Approaches I Tried (And Their Problems)

1. Using validateOnMount

<Formik validateOnMount />
Enter fullscreen mode Exit fullscreen mode

❌ Problem

This helps only during initial mount.

It does not reliably solve:

  • dynamic schema switching
  • multi-step validation transitions

Especially when fields change between steps.


2. Validating Step Fields Manually

const isStepValid = currentStepFields.every(
  (field) => !formik.errors[field]
);
Enter fullscreen mode Exit fullscreen mode

❌ Problem

This adds a lot of manual logic:

  • field tracking
  • nested fields handling
  • dynamic arrays
  • conditional fields

It works, but becomes difficult to maintain in large forms.


3. Using only validateForm()

useEffect(() => {
  validateForm();
}, [currentStep]);
Enter fullscreen mode Exit fullscreen mode

❌ Problem

This triggered validation immediately when moving to the next step, causing all fields in the new step to instantly show validation errors before the user interacted with them.


💡 What I Learned

Formik works great for standard forms.

But with:

  • dynamic validation schemas
  • multi-step flows
  • conditional rendering

👉 you sometimes need to manually synchronize validation state.

The tricky part wasn’t validation itself — it was getting Formik to fully recognize the schema transition.


🚀 Final Thoughts

Debugging this issue made me realize that multi-step forms often require more control over Formik’s internal lifecycle than standard forms do.

Even though the validation schema was updating correctly, Formik wasn’t immediately recalculating the validation state for the newly rendered step.

Using:

resetForm({ values });
await validateForm();
Enter fullscreen mode Exit fullscreen mode

helped ensure that both the form state and validation state stayed in sync during step transitions.

If you’ve run into similar issues while building multi-step forms with Formik, I’d be interested to hear what approach worked for you.

Top comments (1)

Collapse
 
shivani_ravikumar profile image
Shivani R

Hi folks,
If anyone would like to see the complete implementation for better context, let me know — happy to share the full code and structure I used for the multi-step form setup 🚀