Javascript 如何使用React管理多步骤表单?

Javascript 如何使用React管理多步骤表单?,javascript,reactjs,material-ui,Javascript,Reactjs,Material Ui,以下是我的多步骤表单的代码: import clsx from 'clsx'; import React from 'react'; import PropTypes from 'prop-types'; import { makeStyles, withStyles } from '@material-ui/styles'; import Step from '@material-ui/core/Step'; import Stepper from '@material-ui/core/Ste

以下是我的多步骤表单的代码:

import clsx from 'clsx';
import React from 'react';
import PropTypes from 'prop-types';
import { makeStyles, withStyles } from '@material-ui/styles';
import Step from '@material-ui/core/Step';
import Stepper from '@material-ui/core/Stepper';
import StepLabel from '@material-ui/core/StepLabel';
import StepConnector from '@material-ui/core/StepConnector';
import { Container, Row, Col, Button } from 'react-bootstrap';

import Description from '@material-ui/icons/Description';
import AccountCircle from '@material-ui/icons/AccountCircle';
import DirectionsCar from '@material-ui/icons/DirectionsCar';

import Step1 from '../components/Step1';
import Step2 from '../components/Step2';
import Step3 from '../components/Step3';

const styles = () => ({
  root: {
    width: '90%',
  },
  button: {
    marginRight: '0 auto',
  },
  instructions: {
    marginTop: '0 auto',
    marginBottom: '0 auto',
  },
});

const ColorlibConnector = withStyles({ 
  alternativeLabel: {
    top: 22,
  },
  active: {
    '& $line': {
      backgroundColor: '#00b0ff',
    },
  },
  completed: {
    '& $line': {
      backgroundColor: '#00b0ff',
    },
  },
  line: {
    height: 3,
    border: 0,
    backgroundColor: '#eaeaf0',
    borderRadius: 1,
  },
})(StepConnector);

const useColorlibStepIconStyles = makeStyles({
  root: {
    backgroundColor: '#ccc',
    zIndex: 1,
    color: '#fff',
    width: 50,
    height: 50,
    display: 'flex',
    borderRadius: '50%',
    justifyContent: 'center',
    alignItems: 'center',
  },
  active: {
    backgroundColor: '#00b0ff',
    boxShadow: '0 4px 10px 0 rgba(0,0,0,.25)',
  },
  completed: {
    backgroundColor: '#00b0ff',
  },
});

function ColorlibStepIcon(props) {
  const classes = useColorlibStepIconStyles();
  const { active, completed } = props;

  const icons = {
    1: <AccountCircle />,
    2: <DirectionsCar />,
    3: <Description />,
  };

  return (
    <div
      className={clsx(classes.root, {
        [classes.active]: active,
        [classes.completed]: completed,
      })}
    >
      {icons[String(props.icon)]}
    </div>
  );
}

function getSteps() {
  return ['Dati Assicurato', 'Dati Veicolo', 'Dati Assicurazione'];
}

function getStepContent(step) {
  switch (step) {
    case 0:
      return <Step1/>;
    case 1:
      return <Step2/>;
    case 2:
      return <Step3/>;;
    default:
      return 'Unknown step';
  }
}

class HorizontalLinearStepper extends React.Component {

  constructor(props) {
    super(props);
    this.state = {
      activeStep: 0,
      agencyData: {}
    };
  }

  static propTypes = {
    classes: PropTypes.object,
  };

  isStepOptional = step => {
    return step === 1;
  };

  handleNext = () => {
    const { activeStep } = this.state;
    this.setState({
      activeStep: activeStep + 1,
    });
  };

  handleBack = () => {
    const { activeStep } = this.state;
    this.setState({
      activeStep: activeStep - 1,
    });
  };

  handleReset = () => {
    this.setState({
      activeStep: 0,
    });
  };

  logout = () => {
    localStorage.clear();
    this.props.history.push('/');
  }

  render() {
    const { classes } = this.props;
    const steps = getSteps();
    const { activeStep } = this.state;

    return (
      <Container fluid>
        <div className={classes.root}>
          <Stepper alternativeLabel activeStep={activeStep} connector={<ColorlibConnector />}>
            {steps.map((label, index) => {
              const props = {};

              return (
                <Step key={label} {...props}>
                  <StepLabel StepIconComponent={ColorlibStepIcon}>{label}</StepLabel>
                </Step>
              );
            })}
          </Stepper>

          <div>
            {activeStep === steps.length ? (
              <div style={{textAlign: 'center'}}>
                <h1 style={{textAlign: 'center', paddingTop: 100, color: '#7fc297'}}>
                  TERMINATO
                </h1>
                <h4 style={{textAlign: 'center', paddingTop: 50}}>
                  Tutti gli step sono stati completati con successo! 
                </h4>  
                <h4 style={{textAlign: 'center'}}>  
                  Procedi con la generazione del QR Code.
                </h4>
                <Row style={{marginTop: '40px'}} className='justify-content-center align-items-center text-center'>
                  <Col md={{span: 3}}>
                    <Button 
                      style={{borderRadius: 30, borderWidth: 0, height: 50, width: 150, backgroundColor: '#f32a19', borderColor: '#f32a19'}}
                      disabled={activeStep === 0} 
                      onClick={this.handleReset} 
                    >
                      Annulla
                    </Button>
                  </Col>
                  <Col md={{span: 3}}>
                    <Button
                        style={{borderRadius: 30, borderWidth: 0, height: 50, width: 150, backgroundColor: '#00b0ff'}}
                        onClick={() => console.log('Click')}
                      >
                      Procedi
                    </Button>
                  </Col>
                </Row>
              </div>
            ) : 
            (
              <Container style={{}}>
                <h2 className={classes.instructions}>{getStepContent(activeStep)}</h2>
                <Row className='justify-content-center align-items-center text-center'>
                  <Col md={{span: 3}}>
                    <Button 
                      style={{marginTop: 10, backgroundColor: 'gold', borderRadius: 30, borderWidth: 0, height: 50, width: 150}}
                      disabled={activeStep === 0} 
                      onClick={this.handleBack} 
                    >
                      Indietro
                    </Button>
                  </Col>
                  <Col md={{span: 3}}>
                    {
                      activeStep === steps.length - 1 ?
                      <Button
                        style={{marginTop: 10, borderRadius: 30, borderWidth: 0, height: 50, width: 150, backgroundColor: '#7fc297'}}
                        onClick={this.handleNext}
                      >
                      Finito
                      </Button>
                      :
                      <Button
                        style={{marginTop: 10, backgroundColor: '#00b0ff', borderRadius: 30, borderWidth: 0, height: 50, width: 150}}
                        onClick={this.handleNext}
                      >
                      Avanti
                      </Button>
                    }
                  </Col>
                </Row>
              </Container>
            )}
          </div>
        </div>
      </Container>
    );
  }
}

export default withStyles(styles)(HorizontalLinearStepper);
我需要做的是在用户进入下一步之前检查每一步的错误,这样他们只有在正确完成第一步的情况下才能开始执行表单的第二步,依此类推。。。 我怎样才能一步一步地检查呢

此外,我如何在表单的主要组件中收集我要求的所有数据,以便在用户完成整个表单后处理所有这些数据


这里有一个示例

将所有表单数据保存到您的状态(即使是完成的步骤,如当前步骤)。
为了验证给定的数据,您应该为每个字段安排一个handleChange函数。

我将使用指向同一组件的路由,这样您就可以将旧表单存储在state/reducer/localStorage中(实际上是您想要的任何内容),并且一旦当前步骤确定,我就可以简单地将用户导航到下一个步骤

我已经用这个逻辑做了一个演示


如果我能理解你的问题,我希望它基本上能有所帮助

  • 创建一个多步骤表单,并在每个步骤上对数据进行一些验证,如果失败,则让用户重新填充必要的数据,否则移动到下一个表单数据

  • 在每个阶段,将数据存储在
    状态
    存储
    中,用户完成所有步骤后,您希望在本地使用该数据

由于您选择在本地处理这些数据,我更喜欢
redux-store
而不是
state
,因为有明显的原因,比如在业务逻辑和应用程序的其他部分中使用表单数据

我希望这可能有用


然而,这是一个基本的结构,即使我知道有很多警告和许多东西可以抽象

您的组件层次结构看起来不错,我想添加一个东西来将它们保持在一起,例如,对于可以在主组件状态中创建数组的步骤,如下所示

class Main extends React.Component{
    state = {
      ...OTHER_STATE_PROPERTIES,
      activeStep: 0 | 1| 2,
      steps: [{
        name: 'step-name',
        icon: 'icon-name',
        content: Form1 | Form2 | Form3, // this is optional, you can use getContent too
        data: {}
      }]
    }


    // pass this function as prop to every Form Component
    // We will talk about this function soon
    handleStepSubmit = (stepIndex, data) => {
      this.setState((prevState) => ({
        ...prevState,
        activeIndex: prevState.activeIndex + 1,
        steps: prevState.map((step, index) => {
          if(stepIndex !== index){
            return step; 
          }
          return {
            ...step,
            data
          }
        })
      }))
    }
    //... Other stuff

}

现在,每个表单都应该有自己的
状态
(这样只有表单在输入更改时才能重新呈现)和
表单
,您可以在其中处理输入并验证它们,并使用道具将其发送到父组件步骤,因此我们需要在父组件中添加一个函数
handlestesubmit
函数只有在
onSubmit
调用
表单

如何验证数据取决于您,您可以使用

默认输入属性,如,
  • 必需的
  • 最小值,最大值表示数字
  • 类型(电子邮件)
  • 图案
  • 使用JS逻辑验证状态 使用 是的 这是我喜欢的,通过使用formik,您不必担心
    onChange
    验证以及更多的事情,您可以提供它
    validate
    prop,您可以自己编写验证逻辑,或者
    validationSchema
    prop,其中只需要给出yup模式,如果验证失败,它将不允许if
    onSubmit
    触发,如果表单很简单,您还可以使用它输入属性。我们必须在
    onSubmit

    附言:它维持着一个地方政府

    我们所拥有的 在步骤0中,我们将

    // I am omitting other stuff for understanding
    state:{
       activeStep: 0,
       steps: [
         {data: {}},
         {data: {}},
         {data: {}},
       ]
    }
    
    当用户提交表格1时,我们将

    // I am omitting other stuff for understanding
    state:{
       activeStep: 1,
       steps: [
         {data: formOneData},
         {data: {}},
         {data: {}},
       ]
    }
    
    现在,当
    activeStep
    为3时,您将拥有状态中的所有已验证数据,Horray!!击掌!如果需要,您还可以在下一步中使用前面步骤中的数据,因为我们的所有数据都处于父组件状态

    例子:

    请放松所有大写字母的叫喊,尤其是在标题中。另外,请创建一个最小复制示例。@塔德曼对不起,我已锁定大写字母,但我没有注意到整个问题并非如此。@JaredSmith我刚刚编辑了问题,添加了一个
    code沙盒
    我看到了你的解决方案,但是,当我单击submit按钮时,将生成以下错误:
    警告:无法对未安装的组件执行React状态更新。这是一个no-op,但它表示应用程序中存在内存泄漏。要修复此问题,请取消useEffect清理函数中的所有订阅和异步任务。格式(由Context.Consumer创建)、路由(在src/index.js:12)、交换机(在src/index.js:11)、div(在src/index.js:9)、应用程序(在src/index.js:21)、路由器(由BrowserRouter创建)和BrowserRouter(在src/index.js:20)
    我已经更新了代码沙盒,因为在步骤2之后,
    currentForm
    未定义,所以发生了错误。这段代码并没有为此进行优化,只是给你一个大概的想法。我目前正在做一些非常类似的事情,因为你可以看到完整的实现(查看文件
    strapi/packages/strapi plugin content type builder/admin/src/containers/FormModal/index.js
    ),你会有更好的想法。好的,但是我怎样才能对我的步骤栏进行汇总呢?我现在才看到文件
    index.js
    的链接,其中包含
    未找到的响应
    非常感谢您的详细回答。您能给我一个使用
    Formik
    创建和验证表单的小例子吗?您好,我添加了一个类似于您的用例的例子,其中有两个表单使用
    Formik
    ,带和不带
    是的,希望对最后一个问题有所帮助,如何将我的步骤栏与您的方法集成?在父组件中使用stepper将activeIndex从状态传递到状态,但我不明白。。。你能更新你的
    CodeSandbox
    ?多谢各位
    // I am omitting other stuff for understanding
    state:{
       activeStep: 1,
       steps: [
         {data: formOneData},
         {data: {}},
         {data: {}},
       ]
    }