使用 React Hooks 在 Material-UI Stepper 之间传递数据
Passing data across Material-UI Stepper using React Hooks
我有一个多步骤表单,我想使用 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
:
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 MultiStepForm(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>
);
}
export default MultiStepForm;
此时,子表单可以检查其字段是否有效,然后再进行下一步:
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)
,以便显示未触及的字段的验证错误。
这实际上是我在 Whosebug 上的第一个 post,但这对我帮助很大,我不得不添加它。
在我的例子中,我没有使用 sub-Forms,所以我转而使用 useState 来获得一个对象来放置来自输入的答案。我承认这有点蛮力,但一旦我在钩子中实例化对象后,感觉更容易管理。
const [ edit, setEdit ] = useState({1: '', 2: '', 3: '', 4: '', 5: '', 6: '', 7: '', 8: ''});
const [activeStep, setActiveStep] = useState(0);
const handleInputChange = (e) => {
setEdit({...edit, [e.target.name]: e.target.value});
}
const handleNext = () => {
setActiveStep((prevActiveStep) => prevActiveStep + 1);};
const handleBack = () => {
setActiveStep((prevActiveStep) => prevActiveStep - 1);
};
我知道动态对象会更好,但通过此设置,我可以使用 MUI 输入仅更改名称和值,如下所示:
<Input
name={`${activeStep + 1}`}
variant="standard"
value={edit[`${activeStep + 1}`]}
onChange={(e) => handleInputChange(e)}
/>
这样每一步输入值都会移动,希望对大家有帮助!
我有一个多步骤表单,我想使用 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
:
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 MultiStepForm(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>
);
}
export default MultiStepForm;
此时,子表单可以检查其字段是否有效,然后再进行下一步:
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)
,以便显示未触及的字段的验证错误。
这实际上是我在 Whosebug 上的第一个 post,但这对我帮助很大,我不得不添加它。
在我的例子中,我没有使用 sub-Forms,所以我转而使用 useState 来获得一个对象来放置来自输入的答案。我承认这有点蛮力,但一旦我在钩子中实例化对象后,感觉更容易管理。
const [ edit, setEdit ] = useState({1: '', 2: '', 3: '', 4: '', 5: '', 6: '', 7: '', 8: ''});
const [activeStep, setActiveStep] = useState(0);
const handleInputChange = (e) => {
setEdit({...edit, [e.target.name]: e.target.value});
}
const handleNext = () => {
setActiveStep((prevActiveStep) => prevActiveStep + 1);};
const handleBack = () => {
setActiveStep((prevActiveStep) => prevActiveStep - 1);
};
我知道动态对象会更好,但通过此设置,我可以使用 MUI 输入仅更改名称和值,如下所示:
<Input
name={`${activeStep + 1}`}
variant="standard"
value={edit[`${activeStep + 1}`]}
onChange={(e) => handleInputChange(e)}
/>
这样每一步输入值都会移动,希望对大家有帮助!