For all of you Bootstrap, developers who need to construct a multi-step form in React, here is this simple one.

It was built with Create React App and uses React Hooks and Bootstrap 4.

It displays a linear progress bar at the top. The user fills the details in stages, followed by GDPR information and a Thank You stage.

It is a single component where each stage is rendered according to the stage counter,

{
stage === 4 && ...
}

App.js

import React, { useState } from 'react';
import './App.css';

const stages = ['name', 'contact', 'gdpr', 'thanks'];

function App() {
const [stage, setStage] = useState(0);

const [details, setDetails] = React.useState({
firstName: '',
lastName: '',
email: '',
phone: '',
acceptGDPR: false
});

const processField = (name, value) => {
setDetails({ ...details, [name]: value });
}

return (
<div className="container">
<h1>Register</h1>
<div className="progress mt-3 mb-3">
<div className="progress-bar" role="progressbar" style={{ width: `${ (stage + 1) / stages.length * 100 }%` }} aria-valuenow={ (stage + 1) / stages.length * 100 } aria-valuemin="0" aria-valuemax="100"></div>
</div>
{
stage === 0 &&
<form>
<div className="form-group">
<label htmlFor="firstName">First Name</label>
<input
type="text"
className="form-control"
id="firstName"
aria-describedby="firstNameHelp"
value={details.firstName}
onChange={e => processField('firstName', e.target.value)}
/>
<small id="firstNameHelp" className="form-text text-muted">Required</small>
</div>
<div className="form-group">
<label htmlFor="lastName">Last Name</label>
<input
type="text"
className="form-control"
id="lastName"
aria-describedby="lastNameHelp"
value={details.lastName}
onChange={e => processField('lastName', e.target.value)}
/>
<small id="lastNameHelp" className="form-text text-muted">Required</small>
</div>
<button
type="button"
className="btn btn-primary"
disabled={ !details.firstName && !details.lastName }
onClick={ () => setStage(stage + 1) }
style={{ opacity: !details.firstName && !details.lastName ? 0.2 : 1 }}
>
Next
</button>
</form>
}
{
stage === 1 &&
<form>
<div className="form-group">
<label htmlFor="email">Email</label>
<input
type="text"
className="form-control"
id="email"
aria-describedby="emailHelp"
value={details.email}
onChange={e => processField('email', e.target.value)}
/>
<small id="emailHelp" className="form-text text-muted">Required</small>
</div>
<div className="form-group">
<label htmlFor="phone">Phone</label>
<input
type="text"
className="form-control"
id="phone"
aria-describedby="phoneHelp"
value={details.phone}
onChange={e => processField('phone', e.target.value)}
/>
<small id="phoneHelp" className="form-text text-muted">Optional</small>
</div>
<button
type="button"
className="btn btn-primary"
disabled={ !details.email }
onClick={ () => setStage(stage + 1) }
style={{ opacity: !details.email ? 0.2 : 1 }}
>
Next
</button>
</form>
}
{
stage === 2 &&
<form>
<blockquote className="blockquote mt-3 mb-3">
<p className="mb-0">GDPR protects your data for sure ...</p>
</blockquote>
<div className="form-check mt-3 mb-3">
<input
className="form-check-input"
type="checkbox"
value={ details.acceptGDPR }
id="acceptGDPR"
onChange={e => setDetails({ ...details, acceptGDPR: e.target.value })}
/>
<label className="form-check-label" htmlFor="acceptGDPR">
Accept
</label>
</div>
<button
type="button"
className="btn btn-primary"
disabled={ !details.acceptGDPR }
onClick={ () => setStage(stage + 1) }
style={{ opacity: !details.acceptGDPR ? 0.2 : 1 }}
>
Next
</button>
</form>
}
{
stage === 3 &&
<div>
<div className="alert alert-success mt-3 mb-3" role="alert">
Thank You - { details.firstName }
</div>

<h2>Your Recorded Details</h2>

<dl className="row">
<dt className="col-sm-3">First Name</dt>
<dd className="col-sm-9">{ details.firstName }</dd>

<dt className="col-sm-3">Last Name</dt>
<dd className="col-sm-9">{ details.lastName }</dd>

<dt className="col-sm-3">Email</dt>
<dd className="col-sm-9">{ details.email }</dd>

<dt className="col-sm-3">Phone</dt>
<dd className="col-sm-9">{ details.phone }</dd>

<dt className="col-sm-3">GDPR</dt>
<dd className="col-sm-9">Accepted</dd>
</dl>
</div>
}

</div>
);
}

export default App;

index.js

import 'bootstrap/dist/css/bootstrap.css';
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';

ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();

Screenshots

Open source, Mobile, Web, Cloud, Server - Independent Information Technology and Services Professional http://yoramkornatzky.com kornatzky@gmail.com

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store