最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - How to manage a multi-step form with React? - Stack Overflow

programmeradmin2浏览0评论

Here is the code of my multistep form:

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 '../ponents/Step1';
import Step2 from '../ponents/Step2';
import Step3 from '../ponents/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',
    },
  },
  pleted: {
    '& $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)',
  },
  pleted: {
    backgroundColor: '#00b0ff',
  },
});

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

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

  return (
    <div
      className={clsx(classes.root, {
        [classes.active]: active,
        [classespleted]: pleted,
      })}
    >
      {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 pletati 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);

It is posed of three steps and at each step I ask for many data.


This is the code of one of the Step (they are all the same, the difference are the contents of the input fields):

import React from 'react';
import { Container, Row, Col, Form } from 'react-bootstrap';

export default function Step2(props) {

  return(
    <Container>
      <Row style={{marginTop: '30px'}} className='h-100 justify-content-center align-items-center'>
        <Col md={{ span: 6 }} className='text-center my-auto'>
          <h3 style={{marginBottom: '1rem'}}>Dati Veicolo</h3>
          <Form>
            <Form.Row>
              <Form.Group as={Col}>
                <Form.Control
                  type='text' 
                  placeholder='Marca' 
                  required
                />
              </Form.Group>
              <Form.Group as={Col}>
                <Form.Control
                  type='text' 
                  placeholder='Targa' 
                  required
                />
              </Form.Group>
            </Form.Row>
            <Form.Group>
              <Form.Control
                type='text' 
                placeholder='Paese immatricolazione' 
                required
              />
            </Form.Group>
            <h6 style={{marginBottom: '1rem'}}>Possiede un rimorchio?</h6>              
            <Form.Group>
              <Form.Control
                type='text' 
                placeholder='Targa'
              />
            </Form.Group>
            <Form.Group>
              <Form.Control
                type='text' 
                placeholder='Paese immatricolazione'
              />
            </Form.Group>
          </Form>
        </Col>
      </Row>
    </Container>
  );
}

What I need to do is to check for errors at each step before the users pass to the following one, so that they can start fulfilling the second step of the form only in the case they have pleted the first step correctly, and so on ... How can I do this check step by step?

Moreover how can I collect all the data that I ask for in the main ponent of the form so that I can work with all those data after the users have finished fulfilling the whole form?

At this link there is the example

Here is the code of my multistep form:

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 '../ponents/Step1';
import Step2 from '../ponents/Step2';
import Step3 from '../ponents/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',
    },
  },
  pleted: {
    '& $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)',
  },
  pleted: {
    backgroundColor: '#00b0ff',
  },
});

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

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

  return (
    <div
      className={clsx(classes.root, {
        [classes.active]: active,
        [classes.pleted]: pleted,
      })}
    >
      {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 pletati 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);

It is posed of three steps and at each step I ask for many data.


This is the code of one of the Step (they are all the same, the difference are the contents of the input fields):

import React from 'react';
import { Container, Row, Col, Form } from 'react-bootstrap';

export default function Step2(props) {

  return(
    <Container>
      <Row style={{marginTop: '30px'}} className='h-100 justify-content-center align-items-center'>
        <Col md={{ span: 6 }} className='text-center my-auto'>
          <h3 style={{marginBottom: '1rem'}}>Dati Veicolo</h3>
          <Form>
            <Form.Row>
              <Form.Group as={Col}>
                <Form.Control
                  type='text' 
                  placeholder='Marca' 
                  required
                />
              </Form.Group>
              <Form.Group as={Col}>
                <Form.Control
                  type='text' 
                  placeholder='Targa' 
                  required
                />
              </Form.Group>
            </Form.Row>
            <Form.Group>
              <Form.Control
                type='text' 
                placeholder='Paese immatricolazione' 
                required
              />
            </Form.Group>
            <h6 style={{marginBottom: '1rem'}}>Possiede un rimorchio?</h6>              
            <Form.Group>
              <Form.Control
                type='text' 
                placeholder='Targa'
              />
            </Form.Group>
            <Form.Group>
              <Form.Control
                type='text' 
                placeholder='Paese immatricolazione'
              />
            </Form.Group>
          </Form>
        </Col>
      </Row>
    </Container>
  );
}

What I need to do is to check for errors at each step before the users pass to the following one, so that they can start fulfilling the second step of the form only in the case they have pleted the first step correctly, and so on ... How can I do this check step by step?

Moreover how can I collect all the data that I ask for in the main ponent of the form so that I can work with all those data after the users have finished fulfilling the whole form?

At this link there is the example

Share edited Nov 14, 2019 at 19:11 th3g3ntl3man asked Nov 14, 2019 at 18:41 th3g3ntl3manth3g3ntl3man 2,1066 gold badges33 silver badges57 bronze badges 7
  • 2 Please go easy on the ALL CAPS YELLING, especially in the title. – tadman Commented Nov 14, 2019 at 18:44
  • 4 Also, please create a minimal reproduction example. – Jared Smith Commented Nov 14, 2019 at 18:54
  • @tadman Sorry, I have the caps locked and I didn't notice – th3g3ntl3man Commented Nov 14, 2019 at 18:55
  • At least the whole question wasn't like that. – tadman Commented Nov 14, 2019 at 18:56
  • @JaredSmith I just edit the question adding a codesandbox – th3g3ntl3man Commented Nov 14, 2019 at 19:10
 |  Show 2 more ments

4 Answers 4

Reset to default 4 +50

You Component hierarchy seems good, I would like to add one thing to keep things together, like for steps you can create an Array inside a main ponent state like this

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

}

Now each Form should have its own state (so that only the form gets re-render on input changes) and form, where you can handle input and validate them and send it to parent ponent step using props, so we need to add a function in the parent ponent. handleStepSubmit function will only be called after validation of data on onSubmit call of form

How to validate data is up to you, you can use

default input attributes like,

  1. required
  2. min, max for number
  3. type (email)
  4. pattern

Validate State using JS logic

Use yup

Use Formik with yup

This is what I prefer by using formik you don't have to worry about onChange, validation and many more things, you can provide it validate prop where you can write validation logic yourself or validationSchema prop where just yup schema should be given, it will not allow if onSubmit to trigger if validation fails, you can also input attributes with it, if form is simple. We have to call handleStepSubmit on onSubmit

P.S.: It maintains a local state

What we have

At step 0 we will have

// I am omitting other stuff for understanding
state:{
   activeStep: 0,
   steps: [
     {data: {}},
     {data: {}},
     {data: {}},
   ]
}

When user submits Form 1 we will have

// I am omitting other stuff for understanding
state:{
   activeStep: 1,
   steps: [
     {data: formOneData},
     {data: {}},
     {data: {}},
   ]
}

and so one now when activeStep is 3 you will have all the validated data in the state, Horray!! High-Five! You can also data from previous steps in further step if required as we have it all in our parent ponent state.

Example:

I would use a routing that points to the same ponent so you could store the old form in either your state/reducer/localStorage (in fact anything you want) and once the current step is ok I would simply navigate the user to the next one.

I have made a demo codesandbox with this logic.

I hope it helps

Basically, if I understand your question you would like

  • Create a multi-step form and do some validation of data on every step if fails then let the user refill the necessary data else move to next form data.

  • At every stage store in the state or store and once the user pletes all step then you would like to work locally with that data.

Since you chose to work locally with those data I prefer redux-store to state because for obvious reasons like using the form data in business logic and other parts of the react

I hope this might be useful...

However, this is a basic structure and even I know there are lots of caveats and many things could be abstracted...

Save all form data to your states (even pleted steps like current step). For validating your given data you should arrange a handleChange function for each field.

发布评论

评论列表(0)

  1. 暂无评论