How to create simple multi-step sign in with validation
Tags: react, javascript, webdev, beginners
Published: 2021-03-18 13:57:54

# Introduction
Let's say you need to create a multi-step login form like in gmail. You are using react and the global storage (redux, mobx) for development, and you want to isolate components from each other in order to reuse them in the future. Besides this, you need to add validation to each step. In this article I will show the simplest and most correct, in my opinion, solution. Complete solution you can check [here](https://codesandbox.io/s/eloquent-cloud-pcz55)

# Dependencies
First of all, we need a library for processing the form, in my opinion the best solution is react-hook-forms (https://react-hook-form.com/), the site describes in great detail why this is an excellent solution, i will add on my own that this library has powerful functionality (validations, quick integrations, controller mechanism) and good documentation.
For validation we will use the yup library, it's very powerful and popular library
For global storage i will use little-state-machine, because it's very simple solution and built on a flux architecture. But you can use redux or mobx
To integrate yup validation schemas with react-hook-form you will also need @hookform/resolvers package.

# Let's code
## Project Structure

The example uses the following project structure
- steps <- here will be all form steps
  - Congrats.js <- final step, if sign in is successed
  - Email.js <- First step, enter email to continue sign in
  - Password.js <- Second step, enter password to sign in
- store
  - actions.js <- include all actions, in my case only one for update form state
  - index.js <- include app state, in my case only form state
- App.js <- Main component, in my case include form logic
- index
- App.css <- App styles

## About store

In the storage we will store information about the step of the form and email data. Let's add this information in store/index.js
```javascript
const state = {
  step: "Email",
  email: ""
};

export default state;
```
Now let's add an action to update the form in actions.js
```javascript
const updateFormState = (state, payload) => {
  return {
    ...state,
    ...payload
  };
};

export default updateFormState;

```
Let's add our storage to the application in index.js
```javascript
import { StrictMode } from "react";
import ReactDOM from "react-dom";
import App from "./App";
import { StateMachineProvider, createStore } from "little-state-machine";
import store from "./store";

// create out global form state
createStore(store);

const rootElement = document.getElementById("root");
ReactDOM.render(
  <StrictMode>
    <StateMachineProvider>
      <App />
    </StateMachineProvider>
  </StrictMode>,
  rootElement
);

```

## Base logic

The logic for switching the form, as well as its handlers, will be in App.js (for example only). We need to connect the store to the component in order to receive information about the form and update it.

```javascript
import "./styles.css";
import { useStateMachine } from "little-state-machine";
import updateFormState from "./store/actions";
// Here we import form steps
import EmailStep from "./steps/Email";
import CongratsStep from "./steps/Congrats";
import PasswordStep from "./steps/Password";

export default function App() {
  // use hook for getting form state and actions
  const { state, actions } = useStateMachine({ updateFormState });
  // form handler for email step
  const emailFormHandle = ({ email }) => {
    actions.updateFormState({
      email: email,
      step: "Password"
    });
  };
  // form handler for password step
  const passwordFormHandle = ({ password }) => {
    actions.updateFormState({
      step: "Congrats"
    });
  };
  // sign out handler
  const signOutHandle = () => {
    actions.updateFormState({
      step: "Email"
    });
  };

  return (
    <div>
      {state.step === "Email" && (
        <EmailStep email={state.email} onSubmit={emailFormHandle} />
      )}
      {state.step === "Password" && (
        <PasswordStep onSubmit={passwordFormHandle} />
      )}
      {state.step === "Congrats" && (
        <CongratsStep email={state.email} onSignOut={signOutHandle} />
      )}
    </div>
  );
}

```javascript
Form step components are isolated from each other as much as possible, and can be reused in other parts of the application. All you need is only add default values, if they exists (for email step) and form handler function.

## Steps
### Email
The email entry step is the first step for user authorization. It is necessary to check the validity of the entered email, and remember it in case the user at the step with the password wants to go back and change it a little. This may seem very far-fetched, but when there are a lot of inputs in form, saving their state is very useful to save the user's time. Code with comments over here:

```javascript
import { useForm } from "react-hook-form";
// import our validation library
import * as yup from "yup";
// import integration library
import { yupResolver } from "@hookform/resolvers/yup";
import cn from "classnames";

// validation schema
const Schema = yup.object().shape({
  // it says here that we want to check the input with the name email for the fact that the user will pass a string and this string matches email, you can change validation error message by changing text in email function argument
  email: yup.string().email("Enter valid email please")
});

const EmailStep = (props) => {
  // get form on Submit handler from parent component
  const { onSubmit, email } = props;
  // apply validations schema to react-hook-form form object
  const { errors, register, handleSubmit } = useForm({
    resolver: yupResolver(Schema),
    // if user input his email before we can paste it to input as default value
    defaultValues: {
      email
    }
  });

  //  you can check all validations errors in console
  console.log(errors);
  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <div className="form-group">
        <h2>Enter your email</h2>
      </div>
      <div className="form-group">
        {/* check validation errors */}
        {errors.email && (
          <h4 className="invalid-msg">{errors.email.message}</h4>
        )}
        <input
          // make input invalid if get email validation errors
          className={cn(errors.email && "input-invalid")}
          name="email"
          ref={register}
          placeholder="Your email"
        />
      </div>
      <div className="form-group">
        <button type="submit">Next</button>
      </div>
    </form>
  );
};

export default EmailStep;
```
What you need to know:
* Form validation will be apply after user click on submit button (Next button in my case), but you can change this behavior in form options
* All validation errors are in the error object, which is generated by react-hook-form, the key is input name (email) and value is validation message (Enter valid email please)
* You can use the default validation rules by react-hook-form form object, without any libraries, but yup is more powerful and flexible package.

### Password step

The last step in user authorization. The password should be more that 6 symbols length and include Latin letters.  The code is below:

```javascript
import { useForm } from "react-hook-form";
import * as yup from "yup";
import { yupResolver } from "@hookform/resolvers/yup";
import cn from "classnames";

const Schema = yup.object().shape({
  password: yup
    .string()
    .min(6, "Password is too short")
    .matches(/[a-zA-Z]/, "Password can only contain Latin letters.")
});

const PasswordStep = (props) => {
  const { onSubmit } = props;
  const { errors, register, handleSubmit } = useForm({
    resolver: yupResolver(Schema)
  });

  console.log(errors);
  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <div className="form-group">
        <h2>Enter your password</h2>
      </div>
      <div className="form-group">
        {errors.password && (
          <h4 className="invalid-msg">{errors.password.message}</h4>
        )}
        <input
          className={cn(errors.password && "input-invalid")}
          name="password"
          type="password"
          ref={register}
          placeholder="Your password"
        />
      </div>
      <div className="form-group">
        <button type="submit">Sign In</button>
      </div>
    </form>
  );
};

export default PasswordStep;
```

### Final step
And finally let's show user congrats message
```javascript
const CongratsStep = (props) => {
  const { email, onSignOut } = props;

  return (
    <div className="form-group">
      <h2>
        Hello, {email}
        <button onClick={onSignOut}>Sign Out</button>
      </h2>
      <img src="https://i.giphy.com/6nuiJjOOQBBn2.gif" alt="" />
    </div>
  );
};

export default CongratsStep;
```

# Conclusion
That's all. We create isolated form steps, add default values for email value, add validation rules to every form step and use for this most powerful and popular packages (excluding little-state-machine).
If you interested i can show this examples with typescript, MUI and mobx or redux packages

# P.S.

This is my first article, and english is not my native language, hope everything was clear and you had a pleasant time :) If you have problems with understanding the text (due to the fact that I do not know the language well), you can always look at my [code](https://codesandbox.io/s/eloquent-cloud-pcz55), it says much more than any words