React 30-Project 10: Building User Authentication Forms using React.js and React Router
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 componentRegistrationForm.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">×</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: