December 1, 2023

React Router Navigating Programmatically Using useNavigate Hook

In this post we'll see how to navigate programmatically in React routing using useNavigate() hook.

useNavigate() hook

The useNavigate hook is part of the react-router-dom package which returns a function that lets you navigate programmatically.

const navigate = useNavigate();

Here navigate is the returned function.

Using it you can navigate programmatically, for example going to the Home page-

navigate("/home"); 

You can also pass an optional second options argument.

For example, if you want to navigate relative to path rather than route hierarchy (which is default).

navigate("..", { relative: "path" });

This will navigate up one level in the path, instead of one level in the Route hierarchy.

You can also pass a delta, which is essentially a number passed to go back and forward in the history stack.

navigate(-1); 
is equivalent to hitting the browser's back button
navigate(1);
is equivalent to hitting the browser's forward button

React Router useNavigate hook example

In this example we'll create a navigation with login button and the following functionality.

  • When login button is clicked user should be navigated to login page.
  • Login button should also change to Logout button.
  • On successful login user should be navigated to Users page.
  • On clicking Logout button user should be navigated to login screen and old credentials should be cleared.

Login page on click of Login button.

React Router Navigating Programmatically

After successful login change to Logout

useNavigate Hook in react

Navigation Page

src\components\Routes\Navigation.js

import { NavLink, Outlet } from "react-router-dom"
import "./navigation.css";
import { useNavigate } from "react-router-dom";
const Navigation = () => {

    const navigate = useNavigate();
    const showAuthButton = () => {
        if (sessionStorage.getItem("authenticated")){
            return <button className="btn btn-light ms-3" onClick={handleLogoutClick}>Logout</button>
        }else{
            return <button className="btn btn-light ms-3" onClick={handleLoginClick}>Login</button>
        }
    }
    const handleLogoutClick = () => {
        // clear the user credentials
        sessionStorage.clear();
        navigate("/login");
    }
    const handleLoginClick = () => {
        navigate("/login");
    }
    return(
        <>
            <nav id="menu" className="navbar navbar-expand-lg bg-dark navbar-dark">
                <div className="container-fluid">
                    <button className="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
                        <span className="navbar-toggler-icon"></span>
                    </button>

                    <div className="collapse navbar-collapse" id="navbarNav">
                        <ul className="navbar-nav me-auto">
                            <li className="nav-item">
                                <NavLink 
                                    to="/" 
                                    className={({ isActive }) => isActive ? "active" :""} 
                                    end
                                >
                                    Home
                                </NavLink>
                            </li>
                            <li className="nav-item">
                                <NavLink 
                                    to="/users"
                                    className={({ isActive }) => isActive ? "active" :""} 
                                >
                                    Users
                                </NavLink>
                            </li>
                            <li className="nav-item">
                                <NavLink 
                                    to="/about"
                                    className={({ isActive }) => isActive ? "active" :""} 
                                >
                                    About
                                </NavLink>
                            </li>
                        </ul>
                        <div className="d-flex">
                            {showAuthButton()}
                        </div>
                    </div>
                </div>
            </nav>
            <div className="container">
                <div className="row mt-2">
                    <div className="col-xs-12">
                        <Outlet />
                    </div>
                </div>
            </div>
        </>
    );
}

export default Navigation;

One addition to note here is the showAuthButton() function which shows either the Login button or the Logout button based on whether sessionStorage has the "authenticated" value set as true or not. The "authenticated" value is set as true when the user successfully logins (Done in Login.js).

useNavigate() hook is used to get the function. That is then used in the functions associated with the click event of the Login and Logout button to navigate to "/login".

src\components\Routes\navigation.css

#menu a:link,
#menu a:visited {
    color: gray;
}
#menu a:hover {
    color: white;
}
#menu a.active {
    color:#ebecf0;
}

#menu a {
    text-decoration: none;
}

#menu ul {
    gap: 1rem;
}

Route Definition

src\components\Routes\route.js

import { createBrowserRouter } from "react-router-dom";
import About from "./About";
import Home from "./Home";
import Login from "./Login";
import Navigation from "./Navigation";
import Users from "./Users";

export const router = createBrowserRouter([
    {path: "/", element: <Navigation />,
     children: [
        {index: true, element: <Home /> },
        {path: "login", element: <Login />},
        {path: "users", element: <Users />},
        {path: "about", element: <About />}
     ]
    },        
])

Providing the routes

Provide the route definition to your application using the <RouteProvider> component.

import { RouterProvider } from 'react-router-dom';
import { router } from './components/Routes/route';

function App() {
  return <RouterProvider router={router}></RouterProvider>
}

export default App;

Login Page

src\components\Routes\Login.js

import { useState } from "react";
import { useNavigate } from "react-router-dom";

const user = { email: "test@test.com", password: "password" };
const Login = () => {
    const navigate = useNavigate();
   
    const [formField, setState] = useState({
        email: '',
        passwd: '',
        errors: []
    });
    const handleInputChange = (event) => {
        let errors = [];
        // clear errors if any
        if (formField.errors.length > 0) {
            setState((prevState) => (
                {...prevState, errors}
            ));
        }
        const value = event.target.value;
        const name = event.target.name;
        setState((prevState) => (
            {...prevState, [name]: value}
        ));
    }
    const submitHandler = (event) => {
        event.preventDefault();
        let errors = [];
        //email regular expression pattern
        const emailRegEx = /^\w+([.-]?\w+)*@\w+([.-]?\w+)*(\.\w{2,3})+$/;
        let isEmailValid = emailRegEx.test(formField.email.toLowerCase())
        
        if (!isEmailValid) {
            errors.push("email");
        }
        if(formField.passwd  === ""){
            errors.push("passwd");
        }
        
        setState((prevState) => (
            {...prevState, errors}
        ));
        // If there is error dont submit, return to form
        if (errors.length > 0) {
            return false;
        } else{
            // chack if email and password match
            if(formField.email === user.email && formField.passwd === user.password){
                console.log('user found')
                // set to session storage
                sessionStorage.setItem("authenticated", true);
                navigate("/users");
            }else{
                console.log('user not found')
                errors.push("auth");
            }
        }
    }

    const hasError = (name) => {
        console.log("haserror " + name + formField.errors.indexOf(name));
        return formField.errors.indexOf(name) !== -1;
    }

    return (
        <div className="card col-5 mt-5 mx-auto">
            <div className="card-header bg-info text-white">
            <h2>User Login</h2>
            </div>
            <div className="card-body">
                <form onSubmit={submitHandler}>
                    <div className="mb-3">
                        <label className="form-label" htmlFor="userEmail">Email</label>
                        <input type="text" 
                            className={hasError("email") ? "form-control is-invalid" :"form-control"}  
                            placeholder="Enter Email" 
                            name="email"
                            id="userEmail" 
                            value={formField.email} 
                            onChange={handleInputChange} />
                         <div className={hasError("email") ? "invalid-feedback" : ""}>
                            { hasError("email") ? <span>Please enter valid email</span> : "" }
                         </div>
                    </div>   
                    <div className="mb-2">
                        <label className="form-label" htmlFor="userPassword">Password</label>
                        <input type="password" 
                            className={hasError("passwd") ? "form-control is-invalid" :"form-control"}   
                            placeholder="Enter Password" 
                            name="passwd"
                            id="userPassword" 
                            value={formField.passwd} 
                            onChange={handleInputChange}/>
                        <div className={hasError("passwd") ? "invalid-feedback" : ""}>
                            { hasError("passwd") ? <span>Please enter password</span> : "" }
                         </div>
                    </div> 
                   
                    <div>
                        { hasError("auth") ? <span style={{color: "red"}}>Email and/or Password not correct</span> : "" }
                    
                    </div>
                    <button type="submit" className="btn btn-primary mt-1">Submit</button> 
                </form>
            </div>
        </div>
    )
    
}

export default Login;

Important points to note here are-

  1. For simplicity user information is hardcoded and that is matched for the correct credentials.
  2. When the form is submitted, after checking for errors, entered information is compared with the hardcoded user information. If matches then using navigate, navigation is done to Users page. Value of “authenticated” is also set as true in sessionStorage.
    if(formField.email === user.email && formField.passwd === user.password){
      console.log('user found')
      // set to session storage
      sessionStorage.setItem("authenticated", true);
      navigate("/users");
    }
    
  3. If user information doesn't match then an error is displayed.

If you need more clarity about the login screen and how validation is done, please refer this post- React Bootstrap Login Form With Validations

Home Page

src\components\Routes\Home.js

const Home = () => {
    return (
        <>
            <h2>This is Home page</h2>
        </>
    )
}

export default Home;

Users Page

src\components\Routes\Users.js

const Users = () => {
    return(
        <>
            <h2>This is User page</h2>
        </>
    )
}

export default Users;

About Page

src\components\Routes\About.js

const About = () => {
    return <h2>This is a router demo</h2>
}

export default About;

That's all for the topic React Router Navigating Programmatically Using useNavigate Hook. If something is missing or you have something to share about the topic please write a comment.


You may also like

No comments:

Post a Comment