Closed danielwong2268 closed 5 years ago
Export the schemas as an array, and then just access the right one using page index:
validationSchema={validationSchemas[page]}
Snowier100 , you please said a bit was not?I really problem with it , please for me see a example for me understand it ..
,,,, in Formik I specify error occurred while submit
@luandevpro I'm using the Multistep example and it won't let me go to the next step if there is error from validation.
I think that is the intended behaviour? You wouldn't want the user to proceed to the next page if there are validation errors.
Export the schemas as an array, and then just access the right one using page index:
validationSchema={validationSchemas[page]}
This my attempt at that. https://codesandbox.io/s/k5m5po380v
Export the schemas as an array, and then just access the right one using page index:
validationSchema={validationSchemas[page]}
This my attempt at that. https://codesandbox.io/s/k5m5po380v
I have a similar implementation in react-native, and I've noticed that also in your code, that when one goes back to a previous page, you need to click twice on next button to go to the next page. How can this be fixed?
It would be nice if we had an official example of how to use Wizard Flow with Formik. I've seen different approaches and cool ideas but, as I'm new with Formik, I can't tell what would be the best recommended approach for things like:
Sorry if this comment is out of place.
Export the schemas as an array, and then just access the right one using page index:
validationSchema={validationSchemas[page]}
This my attempt at that. https://codesandbox.io/s/k5m5po380v
Thanks for that! Helped me a great deal π
Export the schemas as an array, and then just access the right one using page index:
validationSchema={validationSchemas[page]}
This my attempt at that. https://codesandbox.io/s/k5m5po380v
Hi, @Davidmycodeguy , good snippet, but how do you submit the form at the final step? Seems the handleSubmit
function is called at every single step.
@sunviwo just conditionally fire the real submit handler if it's the last page
what is the proper solution for this?
@sibelius setting class member (submit type with e.g. "partial validation" or "full validation"), submit form and do validation based on class member value.
If it helps anybody, I have an opinionated implementation here: https://github.com/OperationCode/front-end/blob/master/components/Form/MultiStepForm.js
It demands a certain shape of every "step", but custom prop-types make it a protected foot-gun π
My approach:
Define each step component, their step label and the validationSchema for the step
export const MyStepOne = () => {};
MyStepOne.label = 'stepOne';
MyStepOne.validationSchema = yup.object().shape({})
const steps = [
MyStepOne,
...,
MyStepTen,
];
const MultiStepForm = () => {
const [step, setStep] = useState<number>(0);
const isLastStep = () => {
return step === steps.length - 1;
};
const handleNext = () => {
setStep(step + 1);
};
const handleBack = () => {
if (step !== 0) {
setStep(step - 1);
}
};
const getNavigationButtons = (back: boolean, formikBag: FormikProps<Values>) => {
const { isValid, isSubmitting, handleSubmit } = formikBag;
const isDisabled = !isValid || isSubmitting;
return (
<StyledNavBtn>
{back ? <Button onClick={handleBack}>Go back</Button> : <div></div>}
<Button variant='contained' color='primary' onClick={handleSubmit} disabled={isDisabled}>
Next
</Button>
</StyledNavBtn>
);
};
const handleSubmit = (values: Values, formikBag: FormikProps<Values>) => {
const { setSubmitting } = formikBag;
if (!isLastStep()) {
setSubmitting(false);
handleNext();
return;
}
// TODO - handle final submit
setSubmitting(false);
};
const CurrentStep = steps[step];
const { validationSchema } = CurrentStep;
return (
<Formik
initialValues={{}}
validationSchema={validationSchema}
onSubmit={handleSubmit}
render={(formikBag) => {
return (
<StyledContent>
<CurrentStep />
{getNavigationButtons(step > 0, formikBag)}
</StyledContent>
);
}}
/>
);
}
@sunviwo just conditionally fire the real submit handler if it's the last page
Thanks @kylemh. Hush, I tweaked the code to make it work in React Native and it is working fine except... "conditional submission". I could do this using a state value but I realize state is not accessible from handleSubmit
function. Would you have a suggestion please ?
I don't use React Native, but there's nothing to stop you from access state in a class method like handleSubmit
handleSubmit = (event) => {
const { state } = this;
// stuff
};
Worked. Thanks :)
@sibelius thank you for the example you provided. But am i right that validateForm() from formikBag still must be called in order to get a proper isValid value on each step. Otherwise isValid is true as soon as one has passed the first step.
Would handleSubmit() be a proper place to add this?
const handleSubmit = (values, formikBag) => {
const {setSubmitting, validateForm} = formikBag
if (!isLastStep()){
setSubmitting(false)
handleNext()
validateForm() //Run validation manually on each step
return
}
// Do submit
sleep(300).then(() => {
window.alert(JSON.stringify(values, null, 2))
formikBag.setSubmitting(false)
})
}
No because it should only be validating against the current visible step.
@kylemh the issue that I have here is that once the first step has passed validation, than the Next button that is generated for the next step is enabled, because isValid is true (!!). Not exactly sure why Formik returns isValid = true
on the 2. step, even though the form(step) clearly isn't valid, and also the fact that all the fields are set to touched after the first step is done.
So the initial render of the Next button is enabled and validation only kicks in once that button is clicked, and then it gets disabled.
I'm looking for a solution where validation (naturally) would kick in on initial render of every step, whether user is moving to the next or previous step.
validateForm()
clearly would not help in this case, as errors will be added even before the user has had the chance to modifications.
As a quick aside, I've always been recommended to never disable form submit buttons (although I do this if there's an HTTP request in progress from an existing submit): https://ux.stackexchange.com/a/76306
Regardless, you can read this and see when validation is run on the entire form: https://jaredpalmer.com/formik/docs/guides/validation#when-does-validation-run
If you'd like, you can use form.validateForm()
on each step render to prevent a prime user experience.
For anyone using the method proposed above by @sibelius (which is brilliantly simple!), make sure you set your initialValues
for your fields if you're using Material UI and more specifically formik-material-ui. The touched
property of each field wasn't getting set and errors weren't displaying until we set initialValues
.
@arash87 @kylemh I'm having the same issue. After the first step, the form Next button is valid and we disable the button on an invalid form.
Did you figure out where to run validation or reset the isValid state?
Looks like using validateForm()
in handleSubmit
seems to work fine. Anyone have issues with it?
No because it should only be validating against the current visible step.
I thought this was the point? π€
@sibelius's approach looks great, but it doesn't run any validations for me :/
@arianitu @Bulletninja
Nothing is wrong with Sibelius' approach. It works for me with and validation work fine too. Ensure y'all have initialValues set π€·
Maybe this will help? It's not very abstracted, but still:
https://github.com/OperationCode/front-end/blob/master/components/Form/MultiStepForm.js
Yeah, you're probably right. I got started with this huge form using this as a reference https://dev.to/nicholasperetti/the-concept-of-subforms-with-react-and-formik-55mh and was trying to make Sibelius' approach fit with it. But had no luck. Thanks for that example ππ»
Export the schemas as an array, and then just access the right one using page index:
validationSchema={validationSchemas[page]}
This my attempt at that. https://codesandbox.io/s/k5m5po380v
Thanks for that! Helped me a great deal π
When moving to Previous Step again error is not being updated according to the schema form
My implementation, not really good but I hope it helps somebody got the idea from the comments above render() {
const schemaArray = [
SchemaPersonalInfo,
SchemaAddress,
SchemaEducation,
SchemaEmploymentDetails,
SchemaAdditionalInfo ];
const formData = FormData;
let isSaving = this.state.isSaving;
let isSaved = this.state.showSuccessMessage;
let currentTab = this.state.key;
return(
<div className="container bg-white mt-4 mb-5 pb-2 div-shadow" >
<div className="row>">
<div className="col-sm-12">
<Formik
initialValues={formData}
validationSchema={schemaArray[currentTab - 1]}
validateOnBlur={true}
validateOnChange={false}
enableReinitialize={true}
onSubmit={this.saveEmployee}>
{(props) => {
console.log(props);
return(
<Form>
<Tabs
activeKey={this.state.key}
onSelect={this.handleTabSelect}
id="controlled-tab-example" >
<Tab eventKey={1} title="Personal Information" disabled>
<PersonalInfo />
</Tab>
<Tab eventKey={2} title="Address" disabled>
<Address />
</Tab>
<Tab eventKey={3} title="Educational Background" disabled>
<Education />
</Tab>
<Tab eventKey={4} title="Employment Details" disabled>
<EmploymentDetails />
</Tab>
<Tab eventKey={5} title="Additional Info" disabled>
<AdditionalInfo />
</Tab>
</Tabs>
<div className="cl-lg-12 justify-content-center">
{currentTab !=1 ?
<Button
variant="danger"
className="m-2 btn-lg"
disabled={currentTab === 1 || isSaving}
type="button"
onClick={()=>this.handlePreviousButton(currentTab)} >
<FontAwesomeIcon icon={faArrowCircleLeft} /> Prev
</Button> : null}
{currentTab === 5 ? null :
<Button
variant="primary"
className="m-2 btn-lg"
disabled={currentTab === 5}
type="button"
onClick={() => {
props.validateForm()
.then(validation => {
if(this.hasNoError(validation)) this.handleNextButton(currentTab);
props.setTouched(validation)
})
}}> Next <FontAwesomeIcon icon={faArrowCircleRight} />
</Button> }
{ currentTab === 5 ?
<Button
variant="primary"
className="mt-2 mb-2 btn-block"
disabled={isSaving || isSaved}
type="submit">
{isSaving ? 'Savingβ¦' : 'Save'}
</Button> : null }
</div>
</Form>)
}}
</Formik>
</div>
</div>
</div>
)
}
@sibelius after adding setTouched({}) in the if statement inside the submit function works for me, please tell me if there is something worng with it
Export the schemas as an array, and then just access the right one using page index:
validationSchema={validationSchemas[page]}
This my attempt at that. https://codesandbox.io/s/k5m5po380v
I have a similar implementation in react-native, and I've noticed that also in your code, that when one goes back to a previous page, you need to click twice on next button to go to the next page. How can this be fixed?
import React from "react"; import ReactDOM from "react-dom"; import { Formik, Form, Field, ErrorMessage } from "formik"; import * as Yup from "yup";
function App() { const [step,setStep] = React.useState(0); const [lastStep,setLastStep] = React.useState(1);
const SignUpSchema1 = Yup.object().shape({ firstName: Yup.string() .required("Firstname is required"),
lastName: Yup.string()
.required("Lastname is required")
});
const SignUpSchema2 = Yup.object().shape({
email:Yup.string().email().required("Email is required"),
password:Yup.string()
.required("Password is required")
});
const SignUpSchema=[SignUpSchema1,SignUpSchema2] const initialValues = { firstName:"", lastName:"", email: "", password: "",
};
const handleSubmit = (values, formikbag) => {
if(step===1){
console.log("FINAL",values)
}
setTimeout(() => {
formikbag.setSubmitting(false);
}, 1000);
}; const onBack =()=>{ setStep(0);
}
const nextStep = (props) => { // Next Step dont go over Max step console.log(props.errors); console.log("Next step"); console.log(Object.keys(props.errors).length) props.submitForm().then(() => { if (props.isValid && props.values.firstName!=="" && props.values.lastName!=="") { setStep(1); setLastStep(2) props.validateForm(); props.setTouched({}); } }); if(lastStep===2 && props.values.firstName!=="" && props.values.lastName!==""){ setTimeout(()=>{ setStep(1); props.validateForm(); props.setTouched({}); },100) }
};
const step1 = props => { if(step===0){ return(
)
}
}
const step2 = props => { if(step===1){ return(
)
}
} return (
); } export default App
const handleSubmit = async (values, bag) => {
if (step.props.onSubmit) {
// a server error is set here.
// (by the step's submit method).
await step.props.onSubmit(values, bag);
}
if (isLastStep) {
return props.onSubmit(values, bag);
} else {
// the wizard moves to the next step even if there
// are server side errors set here.
bag.setTouched({});
next(values);
}
};
This is my problem. The wizard doesn't check for server side errors inbetween steps. Possible solution is to check if the form is valid before going to the next step. However, is this possible?
What could possibly be the solution?
if (step.props.onSubmit) {
// OVER HERE
await step.props.onSubmit(values, bag);
}
If I return Promise.reject()
here, then the submit handler stops and the wizard doesn't move to the next step.
However, this seems like a hacky solution.
@jaredpalmer could there be an example which also takes care of server side errors?
I am currently catching server side errors like this (instead of catching errors and setting them inside the onSubmit method). This looks clean to me. Please let me know if this is the best way to do this.
const handleSubmit = async (values, bag) => {
try {
if (step.props.onSubmit) {
await step.props.onSubmit(values);
}
if (isLastStep) {
await props.onSubmit(values);
} else {
bag.setTouched({});
next(values);
}
} catch (err) {
// handle server side errors.
const resp = err.response;
if (typeof resp === "object" && "data" in resp) {
bag.setErrors(transformErrors(resp.data));
}
}
};
In the wizard example, I expected there to be validation on each step. As in, if you don't complete the form you can't continue to the next piece of the form.
What would be the idiomatic Formik way to do this? Can we add this to the examples?
Also the documentation (readme) doesn't mention Wizard. It would be nice to add.