首页 > 解决方案 > 使用 React Hooks 跨 Material-UI Stepper 传递数据

问题描述

我有一个多步骤表单,我想在 React 中使用FormikMaterial-ui、功能组件和getState钩子来实现。

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 )建筑看起来很笨拙?

标签: reactjsmaterial-uistatereact-hooksformik

解决方案


好的,我让它工作了,这很有趣(对于小的乐趣值)。一半的问题是认识到需要将activeStep值、、handleNext()handleBack()函数传递给子表单,以及预先计算 this 是否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)子表单何时无效,以便未触及的字段显示其验证错误。


推荐阅读