Login form example with API using React Hooks
In this article, we will show you how to create a simple login form example with API using React Hooks. We created a similar example using react class component but here we will use React Hooks and react-router-dom v6.
In this demonstration, we’ll carry out backend activities using the Node.js API that was previously developed. Therefore, we will concentrate on front-end implementation.
Demo Application
Steps to create login form example with API using React Hooks
- Project structure
- Package version
- Setup secure REST API
- Create functional components and route them
- Setup authenticated routes
- Output
1. Project structure
Refer to the following project structure for login example.
- login-app-react-hooks
- node_modules
- public
- index.html
- src
- pages
- Dashboard.js
- Home.js
- Login.js
- NotFound.js
- utils
- common.js
- PrivateRoutes.js
- PublicRoutes.js
- App.js
- index.css
- index.js
- pages
- package-lock.json
- package.json
- README.md
2. Package version
In this example, we have used the following packages.
3. Setup secure REST API
To create a login form, we need secure REST API to integrate into the application. So we have already created the REST API in Node.js for authentication. Kindly refer the below article to create it or clone it from the GitHub repository.
Login App – Create REST API for authentication in Node.js using JWT
After that just run the project so we can consume the REST API. It will execute on port 4000.
4. Create functional components and route them
Now, It’s time to create components for Home, Login and Dashboard pages and link it using the routing.
- Home component (Access it with/without login) – It’s a simple component which we can access it with/without user login. By default we’ll open this component.
- Login component (Access it without login only) – Here we will create a login page. So with the help of it we can call the REST API and on successful call we will redirect it on the Dashboard component.
- Dashboard component (Access it after login only) – It’s a private page that users can access only after successfully logged in. We will also provide the option to logout from user account in the same component.
If you don’t know how to implement react-router-dom v6 in react app then refer to the following link for more information.
Routing in React using React Router v6
For demo purpose we are creating these components and updating the css in “src” directory.
1 2 3 4 5 6 7 8 9 | import React from 'react'; const Home = () => { return <div> Welcome to the Home Page! </div> } export default Home; |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | import React, { useState } from 'react'; import { useNavigate } from 'react-router-dom'; const Login = props => { const history = useNavigate(); const username = useFormInput(''); const password = useFormInput(''); const [error, setError] = useState(null); const [loading, setLoading] = useState(false); // handle button click of login form const handleLogin = () => { history('/dashboard'); } return ( <div> Login<br /><br /> <div> Username<br /> <input type="text" {...username} autoComplete="new-password" /> </div> <div style={{ marginTop: 10 }}> Password<br /> <input type="password" {...password} autoComplete="new-password" /> </div> {error && <><small style={{ color: 'red' }}>{error}</small><br /></>}<br /> <input type="button" value={loading ? 'Loading...' : 'Login'} onClick={handleLogin} disabled={loading} /><br /> </div> ); } const useFormInput = initialValue => { const [value, setValue] = useState(initialValue); const handleChange = e => { setValue(e.target.value); } return { value, onChange: handleChange } } export default Login; |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | import React from 'react'; import { useNavigate } from 'react-router-dom'; const Dashboard = props => { const history = useNavigate(); // handle click event of logout button const handleLogout = () => { history('/login'); } return ( <div> Welcome User!<br /><br /> <input type="button" onClick={handleLogout} value="Logout" /> </div> ); } export default Dashboard; |
1 2 3 4 5 6 7 8 9 10 | import React from 'react'; const NotFound = () => { return <div> <h2 style={{ marginBottom: 0 }}>404</h2> <h4 style={{ marginTop: 0 }}>Page Not Found!</h4> </div> } export default NotFound; |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | import React from 'react'; import { BrowserRouter, Routes, Route, NavLink } from 'react-router-dom'; import Login from './pages/Login'; import Dashboard from './pages/Dashboard'; import Home from './pages/Home'; import NotFound from './pages/NotFound'; function App() { return ( <BrowserRouter> <div> <div className="header"> <NavLink className={({ isActive }) => isActive ? 'active' : ''} to="/">Home</NavLink> <NavLink className={({ isActive }) => isActive ? 'active' : ''} to="/login">Login</NavLink><small>(Access without token only)</small> <NavLink className={({ isActive }) => isActive ? 'active' : ''} to="/dashboard">Dashboard</NavLink><small>(Access with token only)</small> </div> <div className="content"> <Routes> <Route path="*" element={<NotFound />} /> <Route index element={<Home />} /> <Route path="/login" element={<Login />} /> <Route path="/dashboard" element={<Dashboard />} /> </Routes> </div> </div> </BrowserRouter> ); } export default App; |
1 2 3 4 5 6 7 8 9 10 11 | import React from 'react'; import ReactDOM from 'react-dom/client'; import './index.css'; import App from './App'; const root = ReactDOM.createRoot(document.getElementById('root')); root.render( <React.StrictMode> <App /> </React.StrictMode> ); |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | body { margin: 0; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } code { font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace; } .content { padding: 20px; } .header { padding: 10px; background: #edf2f4; border-bottom: 1px solid #999; } .header a { color: #0072ff; text-decoration: none; margin-left: 20px; margin-right: 5px; } .header a:hover { color: #8a0f53; } .header small { color: #666; } .header .active { color: #2c7613; } |
5. Steup authenticated routes
Let’s integrate the secure API to the react application. Follow the below steps to do it.
Create common utils
To manage the login and logout functionality, we need to create a few functions in “common.js” file which will help us to manage token and user data using
sessionStorage
. Here we will create this file in “src/utils” directory.src/utils/common.js1234567891011121314151617181920212223// return the user data from the session storageexport const getUser = () => {const userStr = sessionStorage.getItem('user');if (userStr) return JSON.parse(userStr);else return null;}// return the token from the session storageexport const getToken = () => {return sessionStorage.getItem('token') || null;}// remove the token and user from the session storageexport const removeUserSession = () => {sessionStorage.removeItem('token');sessionStorage.removeItem('user');}// set the token and user from the session storageexport const setUserSession = (token, user) => {sessionStorage.setItem('token', token);sessionStorage.setItem('user', JSON.stringify(user));}Integrate sign in API
Let’s integrate the “/users/signin” API on the click event of the login button. Refer to the below link if you don’t know how to call API in ReactJS.
Here, we used the axios for the API call. After successful login, we are storing the response data in sessionStorage and redirect it to the dashboard page. Also managing the loading and error flag via
useState
.src/pages/Login.js123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657import React, { useState } from 'react';import { useNavigate } from 'react-router-dom';import { setUserSession } from '../utils/common';import axios from 'axios';const Login = props => {const history = useNavigate();const username = useFormInput('');const password = useFormInput('');const [error, setError] = useState(null);const [loading, setLoading] = useState(false);// handle button click of login formconst handleLogin = () => {setError(null);setLoading(true);axios.post('http://localhost:4000/users/signin', { username: username.value, password: password.value }).then(response => {setLoading(false);setUserSession(response.data.token, response.data.user);history('/dashboard');}).catch(error => {setLoading(false);if (error.response.status === 401) setError(error.response.data.message);else setError("Something went wrong. Please try again later.");});}return (<div>Login<br /><br /><div>Username<br /><input type="text" {...username} autoComplete="new-password" /></div><div style={{ marginTop: 10 }}>Password<br /><input type="password" {...password} autoComplete="new-password" /></div>{error && <><small style={{ color: 'red' }}>{error}</small><br /></>}<br /><input type="button" value={loading ? 'Loading...' : 'Login'} onClick={handleLogin} disabled={loading} /><br /></div>);}const useFormInput = initialValue => {const [value, setValue] = useState(initialValue);const handleChange = e => {setValue(e.target.value);}return {value,onChange: handleChange}}export default Login;Manage the logout in dashboard
On click of the Logout button, we are removing the data from the sessionStorage and redirect it to the login page. Also we are showing the user name on page of the Dashboard.
src/pages/Dashboard.js1234567891011121314151617181920212223import React from 'react';import { useNavigate } from 'react-router-dom';import { getUser, removeUserSession } from '../utils/common';const Dashboard = props => {const history = useNavigate();const user = getUser();// handle click event of logout buttonconst handleLogout = () => {removeUserSession();history('/login');}return (<div>Welcome {user.name}!<br /><br /><input type="button" onClick={handleLogout} value="Logout" /></div>);}export default Dashboard;Create public and private routes
We divided routes in three parts.
- Normal routes – Which we can use to access routes with or without user login.
- Public routes – Which we can use to access the routes without login token only. So if user is already logged-in then we will redirect it to the dashboard page.
- Private routes – Which we can use to access the routes with login token only. So if user is not logged-in then we will redirect back it to the login page.
src/utils/PrivateRoutes.js12345678910import React from 'react';import { Navigate, Outlet } from 'react-router-dom';import { getToken } from './common';// handle the private routesconst PrivateRoutes = () => {return getToken() ? <Outlet /> : <Navigate to="/login" />}export default PrivateRoutes;src/utils/PublicRoutes.js12345678910import React from 'react';import { Navigate, Outlet } from 'react-router-dom';import { getToken } from './common';// handle the public routesconst PublicRoutes = () => {return !getToken() ? <Outlet /> : <Navigate to="/dashboard" />}export default PublicRoutes;Now we have to slightly update the routes in “App.js” to implement authentication.
src/App.js12345678910111213141516171819202122232425262728import PrivateRoutes from './utils/PrivateRoutes';import PublicRoutes from './utils/PublicRoutes';......function App() {......return (<BrowserRouter>......<Routes><Route path="*" element={<NotFound />} /><Route index element={<Home />} /><Route element={<PublicRoutes />}><Route path="/login" element={<Login />} /></Route><Route element={<PrivateRoutes />}><Route path="/dashboard" element={<Dashboard />} /></Route></Routes></BrowserRouter>);}export default App;Handle the browser refresh
At last, we have to handle the browser refresh to authenticate the application. For that we will integrate one more API called “/verifyToken” and manage the authentication flag. Based on the authentication flag we will manage the loading screen.
src/App.js1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859import React, { useState, useEffect } from 'react';import { BrowserRouter, Routes, Route, NavLink } from 'react-router-dom';import axios from 'axios';import PrivateRoutes from './utils/PrivateRoutes';import PublicRoutes from './utils/PublicRoutes';import { getToken, removeUserSession, setUserSession } from './utils/common';import Login from './pages/Login';import Dashboard from './pages/Dashboard';import Home from './pages/Home';import NotFound from './pages/NotFound';function App() {const [authLoading, setAuthLoading] = useState(true);useEffect(() => {const token = getToken();if (!token) {return;}axios.get(`http://localhost:4000/verifyToken?token=${token}`).then(response => {setUserSession(response.data.token, response.data.user);setAuthLoading(false);}).catch(error => {removeUserSession();setAuthLoading(false);});}, []);if (authLoading && getToken()) {return <div className="content">Checking Authentication...</div>}return (<BrowserRouter><div className="header"><NavLink className={({ isActive }) => isActive ? 'active' : ''} to="/">Home</NavLink><NavLink className={({ isActive }) => isActive ? 'active' : ''} to="/login">Login</NavLink><small>(Access without token only)</small><NavLink className={({ isActive }) => isActive ? 'active' : ''} to="/dashboard">Dashboard</NavLink><small>(Access with token only)</small></div><div className="content"><Routes><Route path="*" element={<NotFound />} /><Route index element={<Home />} /><Route element={<PublicRoutes />}><Route path="/login" element={<Login />} /></Route><Route element={<PrivateRoutes />}><Route path="/dashboard" element={<Dashboard />} /></Route></Routes></div></BrowserRouter>);}export default App;
6. Output
Run the login application and check the output in the browser.
I hope you find this article helpful.
Thank you for reading. Happy Coding..!! 🙂