我想使用Formik
,Material-ui
,功能组件和getState
挂钩在React中实现多步骤表单。
import React, { useState, Fragment } from 'react';
import { Button, Stepper, Step, StepLabel } from '@material-ui/core';
import FormPartA from './FormPartA';
import FormPartB from './FormPartB';
import FormPartC from './FormPartC';
function MultiStepForm(props) {
const steps = ['Part A', 'Part B', 'Part C'];
const passedValues = props.values || {};
const [activeStep, setActiveStep] = useState(0);
const [values, setValues] = useState({
field1:(( typeof passedValues.field1 === 'undefined' || passedValues.field1 === null ) ? '1' : passedValues.field1 ),
field2:(( typeof passedValues.field2 === 'undefined' || passedValues.field2 === null ) ? '2' : passedValues.field2 ),
field3:(( typeof passedValues.field3 === 'undefined' || passedValues.field3 === null ) ? '3' : passedValues.field3 ),
field4:(( typeof passedValues.field4 === 'undefined' || passedValues.field4 === null ) ? '4' : passedValues.field4 ),
field5:(( typeof passedValues.field5 === 'undefined' || passedValues.field5 === null ) ? '5' : passedValues.field5 ),
field6:(( typeof passedValues.field6 === 'undefined' || passedValues.field6 === null ) ? '6' : passedValues.field6 )
});
const handleNext = () => {
alert({...props.values, ...values});
setValues({...props.values, ...values});
setActiveStep(activeStep + 1);
};
const handleBack = () => {
setActiveStep(activeStep - 1);
};
function thisStep(step) {
switch (step) {
case 0:
return <FormPartA values={values} setValues={setValues}/>;
case 1:
return <FormPartB values={values} setValues={setValues}/>;
case 2:
return <FormPartC values={values} setValues={setValues}/>;
default:
throw new Error('Mis-step!');
}
}
return (
<div className="MultiStepForm">
<Stepper activeStep={activeStep} className={classes.stepper}>
{steps.map(label => (
<Step key={label}>
<StepLabel>{label}</StepLabel>
</Step>
))}
</Stepper>
<Fragment>
{activeStep === steps.length ? (
<p>You're done!<p>
) : (
<Fragment>
{thisStep(activeStep)}
<div className={classes.buttons}>
{activeStep !== 0 && (
<Button onClick={handleBack} > Back </Button>
)}
<Button onClick={handleNext} >
{activeStep === steps.length - 1 ? 'Done' : 'Next'}
</Button>
</div>
</Fragment>
)}
</Fragment>
</div>
);
}
为了争辩,每个子表单看起来大致如下,每个子表单只有2个字段:
import React from 'react';
import {Formik, useField, Field, Form} from 'formik';
import { TextField } from 'formik-material-ui';
import * as Yup from "yup";
import { Button } from '@material-ui/core';
export default function BasicForm(props) {
const field1 = ( typeof props.values.field1 === 'undefined' || props.values.field1 === null ) ? '' : props.values.field1;
const field2 = ( typeof props.values.field2 === 'undefined' || props.values.field2 === null ) ? '' : props.values.field2;
return (
<div>
<h3>Part A</h3>
<Formik
initialValues={{
field1,
field2
}}
validationSchema={Yup.object({
field1: Yup.string()
.required('Required'),
field2: Yup.string()
.required('Required'),
})}
>
{({submitForm, isSubmitting, values, setFieldValue}) => (
<Form>
<Field name="field1" type="text" label="Field 1" variant="outlined"
margin="normal" fullWidth multiline component={TextField} />
<Field name="field2" type="text" label="Field 2" variant="outlined"
margin="normal" fullWidth multiline component={TextField} />
</Form>
)}
</Formik>
</div>
);
}
让我难过的是状态的更新。在表单之间移动时,如何确保保存了每个子表单的子状态?另外,(( typeof passedValues.field1 === 'undefined' || passedValues.field1 === null ) ? '1' : passedValues.field1 )
的结构看起来很笨拙?
[好,我让它开始工作了,这很有趣(因为小小的乐趣)。一半的问题是认识到activeStep
值,handleNext()
和handleBack()
函数需要传递给子表单,以及预先计算是否isLastStep
:
````javascript
import React, { useState, Fragment } from 'react';
import { Button, Stepper, Step, StepLabel } from '@material-ui/core';
import FormPartA from './FormPartA';
import FormPartB from './FormPartB';
import FormPartC from './FormPartC';
const steps = ['Part A', 'Part B', 'Part C'];
function App(props) {
const { field1, field2, field3, field4, field5, field6, } = props;
const [activeStep, setActiveStep] = useState(0);
const [formValues, setFormValues] = useState({
field1, field2, field3, field4, field5, field6
});
const handleNext = (newValues) => {
setFormValues({ ...formValues, ...newValues });
setActiveStep(activeStep + 1);
};
const handleBack = (newValues) => {
setFormValues({ ...formValues, ...newValues });
setActiveStep(activeStep - 1);
};
function getStepContent(step) {
const isLastStep = (activeStep === steps.length - 1);
switch (step) {
case 0:
return <BasicFormA {...formValues} activeStep={activeStep} isLastStep={isLastStep} handleBack={handleBack} handleNext={handleNext}/>;
case 1:
return <BasicFormB {...formValues} activeStep={activeStep} isLastStep={isLastStep} handleBack={handleBack} handleNext={handleNext}/>;
case 2:
return <BasicFormC {...formValues} activeStep={activeStep} isLastStep={isLastStep} handleBack={handleBack} handleNext={handleNext}/>;
default:
throw new Error('Mis-step!');
}
}
return (
<div className="MultiStepForm">
<Stepper activeStep={activeStep} className={classes.stepper}>
{steps.map(label => (
<Step key={label}>
<StepLabel>{label}</StepLabel>
</Step>
))}
</Stepper>
<Fragment>
{activeStep === steps.length ? (
<p>You're done!<p>
) : (
<Fragment> {getStepContent(activeStep)} <Fragment>
)}
<Fragment>
</div>
);}
导出默认应用程序;
At this point, the sub-form can check that its fields are valid, before going to the next step:
````javascript
import React from 'react';
import {Formik, useField, Field, Form} from 'formik';
import { TextField } from 'formik-material-ui';
import * as Yup from "yup";
import { Button } from '@material-ui/core';
export default function BasicForm(props) {
const { values, field1, field2, activeStep, isLastStep, handleBack, handleNext } = props;
return (
<div>
<Formik
initialValues={{
field1,
field2
}}
validationSchema={Yup.object({
field1: Yup.string()
.required('Required'),
field2: Yup.string()
.required('Required'),
})}
>
{({submitForm, validateForm, setTouched, isSubmitting, values, setFieldValue}) => (
<Form>
<Field name="field1" type="text" label="Field 1" variant="outlined" margin="normal" fullWidth multiline component={TextField} />
<Field name="field2" type="text" label="Field 2" variant="outlined" margin="normal" fullWidth multiline component={TextField} />
</Form>
<div>
{activeStep !== 0 && (
<Button onClick={() => { handleBack(values) } } className={classes.button}> Back </Button>
)}
<Button className={classes.button} variant="contained" color="primary"
onClick={
() => validateForm()
.then((errors) => {
if(Object.entries(errors).length === 0 && errors.constructor === Object ) {
handleNext(values);
} else {
setTouched(errors);
}
})
} >
{isLastStep ? 'Submit Draft' : 'Next'}
</Button>
</div>
)}
</Formik>
</div>
);
}
唯一的窍门是当子表单无效时记住setTouched(errors)
,以便未修改的字段显示其验证错误。