Forms are the standard method used to collect user inputs on web applications. However, sometimes we may be collecting large amounts that may result in a very large form with several fields. This can be a pain not just for the user but for the developer as well since they will have to fit these fields into the form in a way that still looks appealing to the user.
The solution? Break the form into several sections collecting a certain type of information at each point.
undefined
Fortunately for us, react makes this very simple to implement. We can do this by breaking down the sections(steps) into individual react components that collect certain inputs. We can then choose which components are rendered at each step by manipulating state. In this tutorial we shall walk through how we can do this as well as sharing functionality across components using an example of a registeration form.
Getting Started
For this tutorial we shall initiate our project using create-react-app which shall take care of configuring our react development environment as well as provide some boilerplate to get us started. To install create-react-app simply run the following script in your terminal:
npm install -g create-react-app
Now that we have create-react-app installed. The next step is to initialize our application. To do this run
create-react-app multistep-form
This will create a sample React application with the development environment fully configured. The folder structure will look like this
|---public
|------favicon.ico
|------index.html
|------manifest.json
|---src
|------app.css
|------app.js
|------app.test.js
|------index.css
|------index.js #
|------logo.svg
|------registerServiceWorker.js
|---.gitignore
|---package.json
|---README.md
|---yarn.lock
We’ll also be using yarn as our default package manager. Yarn is a great alternative to npm as it locks down dependency versions — a feature that was added to recent versions of npm. Yarn is also the default package manager for create-react-app. If you don’t have yarn yet, you can find the installation instructions here.
For styling we shall use Semantic UI which is a great UI framework easily pluggable into React applications using Semantic UI React. Semantic UI React provides several prebuilt components that we can use to speed up our development process. The framework also supports responsiveness which makes it great for building cross-platform websites. To install it run
yarn add semantic-ui-react
To add the custom semantic ui css add the following to the head section of our index.html
file.
// index.html
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.2.12/semantic.min.css"></link>
Great, now we’re all set to go.
Creating the components
Now that the project is set up let’s create a components
folder under the src
directory in which we’ll place our form components.
Inside it it create 5 files: Confirmation.jsx
, MainForm.jsx
, PersonalDetails.jsx
, Success.jsx
, and UserDetails.jsx
. Feel free to define empty class components in each of these files that we’ll add more functionality to as we work through this tutorial.
Now let’s edit app.js
to import our components and render them. Delete the boilerplate code added by
create-react-app and have the following code instead.
// app.js
import React, { Component } from 'react';
import './App.css';
import MainForm from './components/MainForm';
import { Container } from 'semantic-ui-react';
class App extends Component {
render() {
return(
<Container textAlign='center'>
<MainForm />
</Container> )
}
}
export default App;
We’ve wrapped the form in a Container
component from Semantic UI React which makes it more presentable by centering the text and adding padding.
The first component we’ll set up is the MainForm
component which will be in charge of most of the functionality in our application.
// MainForm.jsx
import React, { Component } from 'react';
import UserDetails from './UserDetails';
import PersonalDetails from './PersonalDetails';
import Confirmation from './Confirmation';
import Success from './Success';
class MainForm extends Component {
state = {
step: 1,
firstName: '',
lastName: '',
email: '',
age: '',
city: '',
country: ''
}
nextStep = () => {
const { step } = this.state
this.setState({
step : step + 1
})
}
prevStep = () => {
const { step } = this.state
this.setState({
step : step - 1
})
}
handleChange = input => event => {
this.setState({ [input] : event.target.value })
}
render(){
const {step} = this.state;
const { firstName, lastName, email, age, city, country } = this.state;
const values = { firstName, lastName, email, age, city, country };
switch(step) {
case 1:
return <UserDetails
nextStep={this.nextStep}
handleChange = {this.handleChange}
values={values}
/>
case 2:
return <PersonalDetails
nextStep={this.nextStep}
prevStep={this.prevStep}
handleChange = {this.handleChange}
values={values}
/>
case 3:
return <Confirmation
nextStep={this.nextStep}
prevStep={this.prevStep}
values={values}
/>
case 4:
return <Success />
}
}
}
export default MainForm;
Great, let’s go through the code we’ve just added. The biggest step we’ve taken towards creating our multistep form is using the switch statement which reads the step from state and uses this to select which components are rendered at each step.
The component is initialized with the default value for step in state as 1
and the first section of our form is rendered. The user can then skip back and forth between steps using the prevStep
and nextStep
functions respectively. These update the value of step
in state so as to allow the user to switch between the rendered components.
The handleChange
function updates the value of the details provided by the user inside state and like the prevStep
and nextStep
functions, it will be passed to the child components as props. This will allow us to pass the functionality implemented for use within the child components as we are about to see in our next step.
Now let’s create the first section of our form. Inside UserDetails.jsx
place the following code.
// UserDetails.jsx
import React, { Component } from 'react';
import { Form, Button } from 'semantic-ui-react';
class UserDetails extends Component{
saveAndContinue = (e) => {
e.preventDefault()
this.props.nextStep()
}
render(){
const { values } = this.props;
return(
<Form >
<h1 className="ui centered">Enter User Details</h1>
<Form.Field>
<label>First Name</label>
<input
placeholder='First Name'
onChange={this.props.handleChange('firstName')}
defaultValue={values.firstName}
/>
</Form.Field>
<Form.Field>
<label>Last Name</label>
<input
placeholder='Last Name'
onChange={this.props.handleChange('lastName')}
defaultValue={values.lastName}
/>
</Form.Field>
<Form.Field>
<label>Email Address</label>
<input
type='email'
placeholder='Email Address'
onChange={this.props.handleChange('email')}
defaultValue={values.email}
/>
</Form.Field>
<Button onClick={this.saveAndContinue}>Save And Continue </Button>
</Form>
)
}
}
export default UserDetails;
This creates a form which collects the user’s first name, last name and email address. The saveAndContinue
function is then in charge of routing us to the next component once we are done filling the details. You will notice that we have called the nextStep
function which we had provided to the component as props, each time this function is called it updates the state of the parent component(MainForm). We also called handleChange
and provided it the name of each field to be updated on each input element.
You may have also noticed that each input field is provided a defaultValue
which it picks from the state of the MainForm
component. This allows it to pick the updated value in state in cases where the user routes back to one step from another as we’ll see in our next component.
Now let’s create our second section, which collects the user’s personal information. Inside PersonalDetails.jsx
add the following code.
// PersonalDetails.jsx
import React, { Component } from 'react';
import { Form, Button } from 'semantic-ui-react';
import { throws } from 'assert';
class PersonalDetails extends Component{
saveAndContinue = (e) => {
e.preventDefault();
this.props.nextStep();
}
back = (e) => {
e.preventDefault();
this.props.prevStep();
}
render(){
const { values } = this.props
return(
<Form color='blue' >
<h1 className="ui centered">Enter Personal Details</h1>
<Form.Field>
<label>Age</label>
<input placeholder='Age'
onChange={this.props.handleChange('age')}
defaultValue={values.age}
/>
</Form.Field>
<Form.Field>
<label>City</label>
<input placeholder='City'
onChange={this.props.handleChange('city')}
defaultValue={values.city}
/>
</Form.Field>
<Form.Field>
<label>Country</label>
<input placeholder='Country'
onChange={this.props.handleChange('country')}
defaultValue={values.country}
/>
</Form.Field>
<Button onClick={this.back}>Back</Button>
<Button onClick={this.saveAndContinue}>Save And Continue </Button>
</Form>
)
}
}
export default PersonalDetails;
This section collects the user’s age and location. The overall functionality is similar to the user details section apart from the addition of the back button which will take us back to the previous step by calling prevStep
from props.
We have the back
and saveAndContinue
functions in each of our components take an event(e) as an arguement and we call event.preventDefault()
in these functions to stop the form from reloading each time we submit which is the default behaviour in forms.
Now let’s create the final section of our form where the user confirms the details they have fed the application are correct. Add the following code to Confirmation.jsx
// Confirmation.jsx
import React, { Component } from 'react';
import { Button, List } from 'semantic-ui-react';
class Confirmation extends Component{
saveAndContinue = (e) => {
e.preventDefault();
this.props.nextStep();
}
back = (e) => {
e.preventDefault();
this.props.prevStep();
}
render(){
const {values: { firstName, lastName, email, age, city, country }} = this.props;
return(
<div>
<h1 className="ui centered">Confirm your Details</h1>
<p>Click Confirm if the following details have been correctly entered</p>
<List>
<List.Item>
<List.Icon name='users' />
<List.Content>First Name: {firstName}</List.Content>
</List.Item>
<List.Item>
<List.Icon name='users' />
<List.Content>Last Name: {lastName}</List.Content>
</List.Item>
<List.Item>
<List.Icon name='mail' />
<List.Content>
<a href='mailto:jack@semantic-ui.com'>{email}</a>
</List.Content>
</List.Item>
<List.Item>
<List.Icon name='calendar' />
<List.Content>{age} Years</List.Content>
</List.Item>
<List.Item>
<List.Icon name='marker' />
<List.Content>{city}, {country}</List.Content>
</List.Item>
</List>
<Button onClick={this.back}>Back</Button>
<Button onClick={this.saveAndContinue}>Confirm</Button>
</div>
)
}
}
export default Confirmation;
This creates a section that displays all the details entered by the user and asks them to confirm the details before submitting them. This is typically where we would make the final API calls to the backend to submit and save our data. Since this is just a demo, the Confirm
button simply implements the same saveAndeContinue
function we used in previous components. However, in a real case scenario we would implement a different submit function that would handle final submission and save the data.
The next and final component in our project is the Success
component which would be rendered when the user successfully saves their information. To create this add the following code to Success.jsx
.
// Success.jsx
import React, { Component } from 'react';
class Success extends Component{
render(){
return(
<div>
<h1 className="ui centered">Details Successfully Saved</h1>
</div>
)
}
}
export default Success;
And that’s it!! We’re good to go. Fire up your app and walk through the steps.
Conclusion
Multistep forms are great when we need to break down a form into sections. They can also allow us to carry out form validation at each step before carrying on to the next step. The use of a case statement for routing between steps eliminates the need for a router which means we can easily plug the form into other components without having to adjust routing. It is also possible to set up API calls in between steps if necessary which opens up a number of new possibilities. You can also handle the state better by adding state management tools such as Redux.
Source: Scotch.io