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 '../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);
It is composed 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 completed 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 component 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
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 component 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 component step using props, so we need to add a function in the parent component. 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
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
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 component state.
I would use a routing that points to the same component 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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With