Create a Multi-Step Form with React and Recoil

We could see multiple steps when filling a form or signing up on a lot of websites. Here, we will discuss the steps to create a multi-step form with React and Recoil state-management tool.

Prerequisites

Before continuing this article, I assume that the reader has a basic understanding of the following technologies:-

What we will learn

After completing this article, we will learn to code a multi-step form with React and Recoil state-management tool. We will create a demo app using this stack.

The app will work the same as given below after all the steps.

Create a multi-step form with React and Recoil

Here in this article, we are not discussing the concepts of React and Recoil. Let us quickly jump into the steps to create a multi-step form with React and Recoil instead.

Create a new React project

The first step is setting up a React application on your system. This can be easily done using the NPX tool.

So, install Node.js on your system first and create a react application using NPX. Don’t bother about the term NPX, because it’s a tool coming with NPM(Node Package Manager) 5.2+ onwards which will install on your system with Node.js itself.

If you need further assistance in the installation of React on your system, use the below links.

Install React on WindowsUbuntu, and macOS

npx create-react-app react-multi-step-form-demo-app

This command will create a react application with the project name react-multi-step-form-demo-app.

Now enter the project directory and start the app.

cd react-multi-step-form-demo-app
npm start

It will open up the React application we have created in our browser window with the address https://localhost:3000. The port may vary if 3000 is busy.

Now we can use our favorite code editor to edit our project. I personally recommend Visual Studio Code.

Install the Recoil package

We can simply install the Recoil package using NPM. Execute the below command in the terminal.

npm install recoil

Wrap the entire app inside RecoilRoot

To start using the Recoil state management tool inside our React app, we need to wrap the entire app inside a RecoilRoot component. If you are already familiar with Redux or Context API, this step will be common.

So inside our App.js component, we will import a component RegisterForm that contains all other form components. This RegisterForm component is wrapped inside the RecoilRoot component.

// src/App.js

import React from "react";
import { RecoilRoot } from "recoil";
import RegisterForm from "./components/RegisterForm";

import "./App.css";
function App() {
  return (
    <RecoilRoot>
      <RegisterForm />
    </RecoilRoot>
  );
}

export default App;

Declare the Atom

We need to create a directory named atoms to store all our atoms. We can simply define an atom as a global state.

Now for our app, let us create an atom file registerForm.js inside the atoms directory. This atom is used to store the form data and the current form step.

  • registerFormInputState – To store the form data globally.
  • registerFormStepState – To store the current form step globally.
// src/atoms/registerForm.js

import { atom } from "recoil";

export const registerFormInputsState = atom({
  key: "registerFormInputsState", // unique ID (with respect to other atoms/selectors)
  default: {
    name: "",
    mobileNumber: "",
    address: "",
  }, // default value (aka initial value)
});

export const registerFormStepState = atom({
  key: "registerFormStepState",
  default: "name",
});

Declare the Selector

Selectors are simply the pure functions that accept atoms, execute any logical operations, and return a result.

Here in our app, we are defining a selector mobileNumberLength that accepts our atom registerFormInputsState, finds the length of the mobile number, and returns it.

// src/selectors/registerForm.js

import { selector } from "recoil";
import { registerFormInputsState } from "../atoms/registerForm";

export const mobileNumberLength = selector({
  key: "mobileNumberLength", // unique ID (with respect to other atoms/selectors)
  get: ({ get }) => {
    const form = get(registerFormInputsState);
    const lengthOfMobileNumber = form.mobileNumber.length;
    return lengthOfMobileNumber;
  },
});

Code the RegisterForm component

In App.js, we have wrapped the RegisterForm component. Let us define this component in this step. So create a file index.js inside the components/RegisterForm directory.

Here, we get the global state registerFormStepState using the useRecoilValue hook and determine the form step, and return the corresponding component.

// src/components/registerForm/index.js

import React from "react";
import { useRecoilValue } from "recoil";
import Name from "./Name";
import MobileNumber from "./MobileNumber";
import Address from "./Address";
import Success from "./Success";

import { registerFormStepState } from "../../atoms/registerForm";

function App() {
  const formStep = useRecoilValue(registerFormStepState);

  return (
    <div className="register-form">
      {formStep === "name" ? (
        <Name />
      ) : formStep === "mobileNumber" ? (
        <MobileNumber />
      ) : formStep === "address" ? (
        <Address />
      ) : formStep === "success" ? (
        <Success />
      ) : null}
    </div>
  );
}

export default App;

Name component to render the first step

Now let us code the Name.jsx with an input field and “Continue” button. Here, we will take the name value from the user and set it globally using the useRecoilState hook.

We are storing all the from in a single state as an object and setting these values using the spread operator function in JS.

// src/components/registerForm/Name.jsx

import React from "react";
import { useRecoilState } from "recoil";
import {
  registerFormInputsState,
  registerFormStepState,
} from "../../atoms/registerForm";

export default function MobileNumber() {
  const [form, setForm] = useRecoilState(registerFormInputsState);
  const [formStep, setFormStep] = useRecoilState(registerFormStepState);

  let handleSubmit = (e) => {
    e.preventDefault();
    setFormStep("mobileNumber");
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        autoFocus
        placeholder="Enter your name"
        onChange={(e) =>
          setForm({
            ...form,
            name: e.target.value,
          })
        }
        value={form.name}
      />
      <button type="submit">Continue</button>
    </form>
  );
}

This will return the view given below.

Second step to show the mobile number input form

To input the mobile number in step two of our form, we need to code a component MobileNumber.jsx inside the components/registerForm directory.

Here we are also utilizing the feature selectors by recoil. We have already defined the selector mobileNumberLength and this will return the length of the mobile number we are entering in real-time.

We are using this length to check whether the length reaches 10. It will be a valid mobile number if then.

// src/components/registerForm/MobileNumber.jsx


import React from "react";
import { useRecoilState, useRecoilValue } from "recoil";
import {
  registerFormInputsState,
  registerFormStepState,
} from "../../atoms/registerForm";
import { mobileNumberLength } from "../../selectors/registerForm";

export default function MobileNumber() {
  const [form, setForm] = useRecoilState(registerFormInputsState);
  const [formStep, setFormStep] = useRecoilState(registerFormStepState);
  const lengthOfMobileNumber = useRecoilValue(mobileNumberLength);

  let handleSubmit = (e) => {
    e.preventDefault();
    setFormStep("address");
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        autoFocus
        placeholder="Enter your mobile number (10 digits)"
        onChange={(e) =>
          setForm({
            ...form,
            mobileNumber: e.target.value,
          })
        }
        value={form.mobileNumber}
      />
      {lengthOfMobileNumber === 10 ? (
        <p className="success-message">Mobile number is valid</p>
      ) : null}
      <button
        type="submit"
        disabled={lengthOfMobileNumber == 10 ? false : true}
      >
        Continue
      </button>
      <button onClick={() => setFormStep("name")}>Back</button>
    </form>
  );
}

The view of the form in this step will look the same as below.

mobile number input form

Address input form in the 3rd step

In step 3, the user must input his/her address to continue. So we are creating a component Address.jsx inside the components/RegisterForm directory.

// src/components/registerForm/Address.jsx

import React from "react";
import { useRecoilState } from "recoil";
import {
  registerFormInputsState,
  registerFormStepState,
} from "../../atoms/registerForm";

export default function MobileNumber() {
  const [form, setForm] = useRecoilState(registerFormInputsState);
  const [formStep, setFormStep] = useRecoilState(registerFormStepState);

  let handleSubmit = (e) => {
    e.preventDefault();
    setFormStep("success");
  };

  return (
    <form onSubmit={handleSubmit}>
      <textarea
        autoFocus
        rows={5}
        type="text"
        placeholder="Enter your address"
        onChange={(e) =>
          setForm({
            ...form,
            address: e.target.value,
          })
        }
        value={form.address}
      />
      <button type="submit">Continue</button>
      <button onClick={() => setFormStep("mobileNumber")}>Back</button>
    </form>
  );
}

This will return the view as given below.

The success step

At the end of this step-by-step form, we will show a success message with all the details given by the user.

We are coding this component Success.jsx inside the components/registerForm directory.

// src/components/registerForm/Success.jsx

import React from "react";
import { useRecoilValue } from "recoil";
import { registerFormInputsState } from "../../atoms/registerForm";
export default function Success() {
  const form = useRecoilValue(registerFormInputsState);

  return (
    <form>
      <div className="success-step">
        <h3>Registration successful!</h3>
        <p>
          <span>Name:</span>
          {form.name}
        </p>
        <p>
          <span>Mobile number:</span>
          {form.mobileNumber}
        </p>
        <p>
          <span>Address:</span>
          {form.address}
        </p>
      </div>
    </form>
  );
}

Add some style

We can add some CSS to make our app a bit shinier. We already imported the App.css file in our App.js file and now let us add some CSS inside it.

// src/App.css

.register-form {
  display: flex;
  justify-content: center;
  margin-top: 10rem;
}
.register-form form {
  border-radius: 0.5rem;
  padding: 2rem;
  box-shadow: 0 2px 5px 0 #dfdfdf, 0 8px 40px 0 rgb(10 14 29 / 6%);
}
.register-form input {
  height: 1rem;
  width: 20rem;
  border-radius: 0.4rem;
  border: 1px solid rgb(214, 214, 214);
  padding: 1rem;
  font-size: 1.2em;
}
.register-form textarea {
  width: 20rem;
  border-radius: 0.4rem;
  border: 1px solid grey;
  padding: 1rem;
  font-size: 1.5em;
  resize: none;
}
.register-form button {
  padding: 1rem;
  display: block;
  margin-top: 1rem;
  width: 100%;
  border: none;
  cursor: pointer;
  border-radius: 0.4rem;
}
.register-form button:hover {
  background-color: rgb(231, 231, 231);
}
.register-form .success-message {
  color: #59bb59;
  margin-top: 0.5rem;
  text-align: center;
}
.register-form .success-step {
  font-size: 1.1em;
}

.register-form .success-step span {
  font-size: 1em;
  color: #afafaf;
  margin-right: 0.8rem;
}

Codesandbox

Refer to the CodeSandbox link to view the live app. You can clone this project to your CodeSandbox account and edit the code also.

https://codesandbox.io/s/competent-cache-df0k5

GitHub

You can always refer to the GitHub repository to clone this project, refer to the code and work on top of it.

https://github.com/techomoro/react-multi-step-form-demo-app

Summary

So in this article, we learned the steps to create a multi-step form with React and Recoil state management tool. We have built a demo app using these stack.

Be the first to reply

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.