Google has a unique login UX that completes the logic flow in two steps, First, where the email is verified, and second where the password verification is done.
In this article, we will see how we can implement the same in Reactjs using Syncfusion’s react components library.
As the login flow suggests, we will have to complete the process in two steps, thus we will have to conditionally render two different components and get their input and validate them before moving to the next step.
Keeping this in mind let us create the structure of the component.
import { useState } from "react"; const LoginForm = () => { const [track, setTrack] = useState(0); // verify the email first and then the password return track === 0 ? <Email /> : <Password />; } export default LoginForm;
Let us create the Email and the Password components separately and then combine them.
Email component
Getting the UI reference from the Google login, the email component will have an input box and the next button that will verify the email and update the state to move to the next step.
We will be creating the same, for the Input box we will be using the TextBoxComponent
from the @syncfusion/ej2-react-inputs
, and for the button, we will be using ButtonComponent
from the @syncfusion/ej2-react-buttons
. You can use any component library of your choice.
There are stylesheets available for these components which help to use all variations of it.
import "@syncfusion/ej2-base/styles/material.css"; import "@syncfusion/ej2-inputs/styles/material.css"; import "@syncfusion/ej2-buttons/styles/material.css";
TextBoxComponent
provides an excellent wrapper around the input elements allowing better control over them with style as well as value. We can pass the type parameter to make it render the desired input field.
The same goes for the ButtonComponent
it comes in multiple variations as well as can be easily extended making it great to work with.
<> <TextBoxComponent type="email" value={email} placeholder="Email" floatLabelType="Auto" input={({ value }) => setEmail(value)} cssClass="e-outline" /> <div className="buttonWrapper"> <ButtonComponent type="submit" cssClass="e-info" style={{ fontSize: "18px", padding: "10px 20px" }} onClick={verifyEmail} > Next </ButtonComponent> </div>{" "} </>;
We want the email input box to be a controlled component and thus we will have to maintain its state.
For the button, we are listening to the onClick event and on its click, we will perform the asynchronous operations to verify the email and then navigate to the next step.
const [email, setEmail] = useState(""); const verifyEmail = async () => { // async operations // validate email if (email) { setTrack(1); } };
Here I am just changing the track if there is some value in the email state, but you can perform all sorts of operations you like.
Password component
As we have created the email component the same way we can use TextBoxComponent
and ButtonComponent
to create the Password component and on its button click, we can perform the final check to authenticate the user.
const [password, setPassword] = useState(""); const handleSubmit = async () => { // async operations }; <> <TextBoxComponent type="password" value={password} placeholder="Password" floatLabelType="Auto" input={({ value }) => setPassword(value)} cssClass="e-outline" key="2" /> <div className="buttonWrapper"> <ButtonComponent type="submit" cssClass="e-danger" onClick={() => setTrack(0)} style={{ fontSize: "18px", padding: "10px 20px" }} > Change Email </ButtonComponent>{" "} <ButtonComponent type="submit" cssClass="e-success" onClick={handleSubmit} style={{ fontSize: "18px", padding: "10px 20px" }} > Submit </ButtonComponent> </div> </>;
Here if you see I have added two buttons, one to navigate back to the email verification part and the other for final verification.
Putting all pieces together.
import { useState } from "react"; import { TextBoxComponent } from "@syncfusion/ej2-react-inputs"; import { ButtonComponent } from "@syncfusion/ej2-react-buttons"; import "@syncfusion/ej2-base/styles/material.css"; import "@syncfusion/ej2-inputs/styles/material.css"; import "@syncfusion/ej2-buttons/styles/material.css"; import "./App.css"; const LoginForm = () => { const [track, setTrack] = useState(0); const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); const verifyEmail = async () => { // async operations // validate email if (email) { setTrack(1); } }; const handleSubmit = async () => { // async operations }; return ( <div className="box"> <div style={{ textAlign: "center" }}> <p style={{ fontSize: "1.5em" }}>Sign In</p> <p>to continue to Gmail.</p> </div> {track === 0 ? ( <> <TextBoxComponent type="email" value={email} placeholder="Email" floatLabelType="Auto" input={({ value }) => setEmail(value)} cssClass="e-outline" /> <div className="buttonWrapper"> <ButtonComponent type="submit" cssClass="e-info" style={{ fontSize: "18px", padding: "10px 20px" }} onClick={verifyEmail} > Next </ButtonComponent> </div>{" "} </> ) : ( <> <TextBoxComponent type="password" value={password} placeholder="Password" floatLabelType="Auto" input={({ value }) => setPassword(value)} cssClass="e-outline" key="2" /> <div className="buttonWrapper"> <ButtonComponent type="submit" cssClass="e-danger" onClick={() => setTrack(0)} style={{ fontSize: "18px", padding: "10px 20px" }} > Change Email </ButtonComponent>{" "} <ButtonComponent type="submit" cssClass="e-success" onClick={handleSubmit} style={{ fontSize: "18px", padding: "10px 20px" }} > Submit </ButtonComponent> </div> </> )} </div> ); }; export default LoginForm;
If you are wondering why I haven’t separated the Email and Password components into two different functions like this.
import { useState } from "react"; import { TextBoxComponent } from "@syncfusion/ej2-react-inputs"; import { ButtonComponent } from "@syncfusion/ej2-react-buttons"; import "@syncfusion/ej2-base/styles/material.css"; import "@syncfusion/ej2-inputs/styles/material.css"; import "@syncfusion/ej2-buttons/styles/material.css"; import "./App.css"; const LoginForm = () => { const [track, setTrack] = useState(0); const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); const verifyEmail = async () => { // async operations // validate email if (email) { setTrack(1); } }; const handleSubmit = async () => { // async operations }; const Email = () => ( <> <TextBoxComponent type="email" value={email} placeholder="Email" floatLabelType="Auto" input={({ value }) => setEmail(value)} cssClass="e-outline" /> <div className="buttonWrapper"> <ButtonComponent type="submit" cssClass="e-info" style={{ fontSize: "18px", padding: "10px 20px" }} onClick={verifyEmail} > Next </ButtonComponent> </div>{" "} </> ); const Password = () => ( <> <TextBoxComponent type="password" value={password} placeholder="Password" floatLabelType="Auto" input={({ value }) => setPassword(value)} cssClass="e-outline" key="2" /> <div className="buttonWrapper"> <ButtonComponent type="submit" cssClass="e-danger" onClick={() => setTrack(0)} style={{ fontSize: "18px", padding: "10px 20px" }} > Change Email </ButtonComponent>{" "} <ButtonComponent type="submit" cssClass="e-success" onClick={handleSubmit} style={{ fontSize: "18px", padding: "10px 20px" }} > Submit </ButtonComponent> </div> </> ); // verify the email first and then the password return track === 0 ? <Email /> : <Password />; }; export default LoginForm;
Well because if I wrap them in a function and then use the global state, it creates a bug, and because of this, the input component loses its focus every time we type something because once the state is updated the component re-renders and because the input field is wrapped inside a function, it is treated as functional component thus it loses it focus.
To solve this we can maintain the state in each component but that will increase complexity as we will have to do the prop drill down or use a global state.