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
-
isValidused 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>
Each step had its own validation schema.
The issue:
- Step 1 becomes valid →
isValid = true - Move to Step 2
- New fields are empty
- ❌
isValidstill remainstrue
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]}
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],
})
const { values, resetForm, validateForm, handleSubmit, isValid } = formik;
useEffect(() => {
const checkFormValidation = async () => {
resetForm({ values });
await validateForm();
};
checkFormValidation();
}, [currentStep]);
<FormikProvider value={formik}>
<Form onSubmit={handleSubmit}>
// form logic
</Form>
</FormikProvider>
✅ 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
-
isValidupdates 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
-
isValidreflects current step state - buttons enable/disable correctly
🤔 Other Approaches I Tried (And Their Problems)
1. Using validateOnMount
<Formik validateOnMount />
❌ 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]
);
❌ 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]);
❌ 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();
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)
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 🚀