Image for post
Image for post

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

Image for post
Image for post
Image for post
Image for post
Image for post
Image for post
Image for post
Image for post
Image for post
Image for post

Written by

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