React 30-Project 10: Building User Authentication Forms using React.js and React Router

Saurabh Mhatre
7 min readNov 9, 2023

--

Title image

Introduction

In this tutorial, we’ll create login and registration forms using React.js and React Router to handle user authentication in frontend.

Goal:

Make authetication forms with which user can login or register for a particular website as shown below:

Connect to backend database for enabling login/registration functionality.
Try building this forms before continuing ahead.

Prerequisites

  • Node.js and npm installed on your system.
  • Basic understanding of JavaScript and React.

Step 1: Set Up a New React App

Create a new React app using Create React App:

npx create-react-app login-registration
cd login-registration

Step 2: Install Required Packages

Install Axios (for making API requests) and React Router:

npm install axios react-router-dom@5.1.2

Step 3: Create Components

Inside the src folder, create the following components in a components folder creating a individual folder for each component

  • AlertComponent.js: Alert component for error messages.
  • Header.js : Common header component
  • RegistrationForm.js: Component for the registration form.
  • LoginForm.js: Component for the login form.
  • Home.js: Component for the home page.

Your folder structure should look like this:

- /src
- /components
- AlertComponent
- AlertComponent.js
- AlertComponent.css
- Header
- Header.js
- RegistrationForm
- RegistrationForm.js
- RegistrationForm.css
- LoginForm
- LoginForm.js
- LoginForm.css
- Home
- Home.js
- Home.css
- App.js
- index.js
- ...

Step 4: Set Up Routes

Modify src/App.js to set up the routes

import React, {useState} from 'react';
import './App.css';
import Header from './components/Header/Header';
import LoginForm from './components/LoginForm/LoginForm';
import RegistrationForm from './components/RegistrationForm/RegistrationForm';
import Home from './components/Home/Home';
import PrivateRoute from './utils/PrivateRoute';
import {
BrowserRouter as Router,
Switch,
Route
} from "react-router-dom";
import AlertComponent from './components/AlertComponent/AlertComponent';
function App() {
const [title, updateTitle] = useState(null);
const [errorMessage, updateErrorMessage] = useState(null);
return (
<Router>
<div className="App">
<Header title={title}/>
<div className="container d-flex align-items-center flex-column">
<Switch>
<Route path="/" exact={true}>
<RegistrationForm showError={updateErrorMessage} updateTitle={updateTitle}/>
</Route>
<Route path="/register">
<RegistrationForm showError={updateErrorMessage} updateTitle={updateTitle}/>
</Route>
<Route path="/login">
<LoginForm showError={updateErrorMessage} updateTitle={updateTitle}/>
</Route>
<PrivateRoute path="/home">
<Home/>
</PrivateRoute>
</Switch>
<AlertComponent errorMessage={errorMessage} hideError={updateErrorMessage}/>
</div>
</div>
</Router>
);
}

export default App;

Setup `App.css` file:

.App {
text-align: center;
}

.hv-center {
display: flex;
justify-content: center;
align-items: center;
}

Step 5: Create Header and Alert Component

Create src/components/Header/Header.js:

import React from 'react';
import { withRouter } from "react-router-dom";
import { ACCESS_TOKEN_NAME } from '../../constants/apiConstants';
function Header(props) {
const capitalize = (s) => {
if (typeof s !== 'string') return ''
return s.charAt(0).toUpperCase() + s.slice(1)
}
let title = capitalize(props.location.pathname.substring(1,props.location.pathname.length))
if(props.location.pathname === '/') {
title = 'Welcome'
}
function renderLogout() {
if(props.location.pathname === '/home'){
return(
<div className="ml-auto">
<button className="btn btn-danger" onClick={() => handleLogout()}>Logout</button>
</div>
)
}
}
function handleLogout() {
localStorage.removeItem(ACCESS_TOKEN_NAME)
props.history.push('/login')
}
return(
<nav className="navbar navbar-dark bg-primary">
<div className="row col-12 d-flex justify-content-center text-white">
<span className="h3">{props.title || title}</span>
{renderLogout()}
</div>
</nav>
)
}
export default withRouter(Header);

Here we are displaying title and logout button which logs out the user.

Create src/AlertComponent/AlertComponent.js:

import React, { useState, useEffect } from 'react';
import './AlertComponent.css';
function AlertComponent(props) {
const [modalDisplay, toggleDisplay] = useState('none');
const openModal = () => {
toggleDisplay('block');
}
const closeModal = () => {
toggleDisplay('none');
props.hideError(null);
}
useEffect(() => {
if(props.errorMessage !== null) {
openModal()
} else {
closeModal()
}
});

return(
<div
className={"alert alert-danger alert-dismissable mt-4"}
role="alert"
id="alertPopUp"
style={{ display: modalDisplay }}
>
<div className="d-flex alertMessage">
<span>{props.errorMessage}</span>
<button type="button" className="close" aria-label="Close" onClick={() => closeModal()}>
<span aria-hidden="true">&times;</span>
</button>
</div>

</div>
)
}

export default AlertComponent

Next create AlertComponent.css in same folder:

.alertMessage {
min-width: 200px;
justify-content: space-between;
}

Step 6: Create Registration Form Component

Create src/components/Registration/RegistrationForm.js:

import React, {useState} from 'react';
import axios from 'axios';
import './RegistrationForm.css';
import {API_BASE_URL, ACCESS_TOKEN_NAME} from '../../constants/apiConstants';
import { withRouter } from "react-router-dom";

function RegistrationForm(props) {
const [state , setState] = useState({
email : "",
password : "",
confirmPassword: "",
userName: "",
successMessage: null
})
const handleChange = (e) => {
const {id , value} = e.target
setState(prevState => ({
...prevState,
[id] : value
}))
}
const sendDetailsToServer = () => {
if(state.email.length && state.password.length) {
props.showError(null);
const payload={
"email":state.email,
"password":state.password,
"name": state.userName
}
axios.post(API_BASE_URL+'/user/register', payload)
.then(function (response) {
if(response.status === 200){
setState(prevState => ({
...prevState,
'successMessage' : 'Registration successful. Redirecting to home page..'
}))
localStorage.setItem(ACCESS_TOKEN_NAME,response.data.token);
redirectToHome();
props.showError(null)
} else{
props.showError("Some error ocurred");
}
})
.catch(function (error) {
console.log(error);
});
} else {
props.showError('Please enter valid username and password')
}

}
const redirectToHome = () => {
props.updateTitle('Home')
props.history.push('/home');
}
const redirectToLogin = () => {
props.updateTitle('Login')
props.history.push('/login');
}
const handleSubmitClick = (e) => {
e.preventDefault();
if(state.password === state.confirmPassword) {
sendDetailsToServer()
} else {
props.showError('Passwords do not match');
}
}
return(
<div className="card col-12 col-lg-4 login-card mt-2 hv-center">
<form>
<div className="form-group text-left">
<div className="form-group text-left">
<label htmlFor="exampleInputPassword1">User Name</label>
<input type="text"
className="form-control"
id="userName"
placeholder="Add User Name"
value={state.userName}
onChange={handleChange}
/>
</div>
<label htmlFor="exampleInputEmail1">Email address</label>
<input type="email"
className="form-control"
id="email"
aria-describedby="emailHelp"
placeholder="Enter email"
value={state.email}
onChange={handleChange}
/>
<small id="emailHelp" className="form-text text-muted">We'll never share your email with anyone else.</small>
</div>
<div className="form-group text-left">
<label htmlFor="exampleInputPassword1">Password</label>
<input type="password"
className="form-control"
id="password"
placeholder="Password"
value={state.password}
onChange={handleChange}
/>
</div>
<div className="form-group text-left">
<label htmlFor="exampleInputPassword1">Confirm Password</label>
<input type="password"
className="form-control"
id="confirmPassword"
placeholder="Confirm Password"
value={state.confirmPassword}
onChange={handleChange}
/>
</div>
<button
type="submit"
className="btn btn-primary"
onClick={handleSubmitClick}
>
Register
</button>
</form>
<div className="alert alert-success mt-2" style={{display: state.successMessage ? 'block' : 'none' }} role="alert">
{state.successMessage}
</div>
<div className="mt-2">
<span>Already have an account? </span>
<span className="loginText" onClick={() => redirectToLogin()}>Login here</span>
</div>

</div>
)
}

export default withRouter(RegistrationForm);

In the handleChange method we use id of form elements to update the related state variables. Next, we send credentials to server in sendDetailsToServer method using axios npm. We store the access token received from server in local storage to use in successive API calls.

Create components/RegistrationForm/RegistrationForm.css :

.loginText {
color: #007bff;
font-weight: bold;
cursor: pointer;
}

.successMessage {
color: green;
font-weight: bold;
}

Step 7: Create Login Form Component

Create src/components/LoginForm/LoginForm.js:

import React, {useState} from 'react';
import axios from 'axios';
import './LoginForm.css';
import {API_BASE_URL, ACCESS_TOKEN_NAME} from '../../constants/apiConstants';
import { withRouter } from "react-router-dom";

function LoginForm(props) {
const [state , setState] = useState({
email : "",
password : "",
successMessage: null
})
const handleChange = (e) => {
const {id , value} = e.target
setState(prevState => ({
...prevState,
[id] : value
}))
}

const handleSubmitClick = (e) => {
e.preventDefault();
const payload={
"email":state.email,
"password":state.password,
}
axios.post(API_BASE_URL+'/user/login', payload)
.then(function (response) {
if(response.status === 200){
setState(prevState => ({
...prevState,
'successMessage' : 'Login successful. Redirecting to home page..'
}))
localStorage.setItem(ACCESS_TOKEN_NAME,response.data.token);
redirectToHome();
props.showError(null)
}
else if(response.code === 204){
props.showError("Username and password do not match");
}
else{
props.showError("Username does not exists");
}
})
.catch(function (error) {
console.log(error);
});
}
const redirectToHome = () => {
props.updateTitle('Home')
props.history.push('/home');
}
const redirectToRegister = () => {
props.history.push('/register');
props.updateTitle('Register');
}
return(
<div className="card col-12 col-lg-4 login-card mt-2 hv-center">
<form>
<div className="form-group text-left">
<label htmlFor="exampleInputEmail1">Email address</label>
<input type="email"
className="form-control"
id="email"
aria-describedby="emailHelp"
placeholder="Enter email"
value={state.email}
onChange={handleChange}
/>
<small id="emailHelp" className="form-text text-muted">We'll never share your email with anyone else.</small>
</div>
<div className="form-group text-left">
<label htmlFor="exampleInputPassword1">Password</label>
<input type="password"
className="form-control"
id="password"
placeholder="Password"
value={state.password}
onChange={handleChange}
/>
</div>
<div className="form-check">
</div>
<button
type="submit"
className="btn btn-primary"
onClick={handleSubmitClick}
>Submit</button>
</form>
<div className="alert alert-success mt-2" style={{display: state.successMessage ? 'block' : 'none' }} role="alert">
{state.successMessage}
</div>
<div className="registerMessage">
<span>Dont have an account? </span>
<span className="loginText" onClick={() => redirectToRegister()}>Register</span>
</div>
</div>
)
}

export default withRouter(LoginForm);

The logic is similar to registration component. We redirect the user to home page after successive login.

Create src/components/LoginForm/LoginForm.css :

.login-card {
min-height: 60vh;
}

.registerMessage {
margin-top: 10vh;
}

Step 8: Create Home Component

Create src/components/Home/Home.js:

import React,{ useEffect } from 'react';
import { withRouter } from 'react-router-dom';
import { ACCESS_TOKEN_NAME, API_BASE_URL } from '../../constants/apiConstants';
import axios from 'axios'
function Home(props) {
useEffect(() => {
axios.get(API_BASE_URL+'/user/me', { headers: { 'token': localStorage.getItem(ACCESS_TOKEN_NAME) }})
.then(function (response) {
if(response.status !== 200){
redirectToLogin()
}
})
.catch(function (error) {
redirectToLogin()
});
},[])
function redirectToLogin() {
props.history.push('/login');
}
return(
<div className="mt-2">
Home page content
</div>
)
}

export default withRouter(Home);

Here we verify user identity using access-token recieved previously when user opens home page.

Step 9: Create PrivateRoute Component

Create src/utils/PrivateRoute.js:

import React from 'react';
import { Redirect, Route } from "react-router-dom";
import { ACCESS_TOKEN_NAME } from '../constants/apiContants';
function PrivateRoute({ children, ...rest }) {
return (
<Route
{...rest}
render={({ location }) =>
localStorage.getItem(ACCESS_TOKEN_NAME) ? (
children
) : (
<Redirect
to={{
pathname: "/login",
state: { from: location }
}}
/>
)
}
/>
);
}

export default PrivateRoute;

This route is created to make sure that app’s internal pages are visible only to authorised users.

Step 10: Create Constants

Create src/constants/apiConstants.js

export const API_BASE_URL = process.env.REACT_APP_SERVER_URL;
export const ACCESS_TOKEN_NAME = 'login_access_token';

Next create a .env file within project folder and add `API_BASE_URL` variable with value as server base URL created in previous project.

Step 11: Start the App

In your terminal, make sure you’re in the project directory and run:

npm start

This will start the development server and open the app in your default web browser.

You should now see a registration page where you can create a new account. When you click on register button with valid credentials you should be redirected to home. Customize the content and styles further to suit your specific product details.

Source code links:
Codesandbox | Github

Conclusion

Congratulations! You’ve successfully built a authentication forms using React.js and React Router. Feel free to explore and customize the app further to enhance your understanding of React. Have a nice day ahead!

Check out my Youtube channel for more content:
SaurabhNative-Youtube

Check out next part in the series to learn about building crypto currency price listing app:

React30-Project11

--

--

Saurabh Mhatre
Saurabh Mhatre

Written by Saurabh Mhatre

Senior Frontend Developer with 9+ years industry experience. Content creator on Youtube and Medium. LinkedIn/Twitter/Instagram: @SaurabhNative

Responses (1)