Make Custom Navigation in React without refreshing a Page - Inspired by react-router

Make Custom Navigation in React without refreshing a Page - Inspired by react-router

If you are a react developer then probably you have used the react-router package to make navigation between components, In this article, we gonna create our own basic react-router kind of thing which will help us to navigate between routes without refreshing a page.

React Router Intro

React router is a library that helps us to perform some action related to navigation, It offers so many things which makes it very amazing. It also offers hooks that are really doing a great job.

You can install the library using following command in your any react project

npm install react-router-dom@6

Note - Above command will install v6 version of react-router, which is a bit different than v5, you might see most of the article about v5 because v6 just has launched. If you want to learn more about react-router v6 then you can go to https://reactrouter.com/.

Now let's move towards our goal.

Create a New React App

npx create-react-app <your project name>

Once your app ready to run then write following command to run your app.

npm start

It will run your app on localhost:3000, you can also change the port number with the following solution.

Go to your package.json file, you will see the start command in the scripts object. by default it will be

"start": "react-scripts start",

If you are in windows then replace it with the following command

"start": "set PORT=3001 && react-scripts start",

For linux/mac replace it with the following command

"start": "export PORT=3001 && react-scripts start",

that's it, now you can just write npm start to start your application.

Now in your app.js file remove everything and just write the code the same as below to see the change.

import React from "react";

const App = () =>{
  return <h1>Hello React</h1>
}

export default App;

You will see the result in your browser.

Now let's create a couple of components in your react app, Create a folder and give it name components

In the components folder create some JSX components and name it anything, I will be creating 4 components which will be Home, About, Signup,Login. And my directory will look like this.

Screenshot (5).png

Now return some jsx elements from all components, See the example of Home component below.

const Home = () =>{
    return <div>
        <h3>Home Route</h3>
    </div>
}

export default Home;

Write the above code in all components and give an appropriate name to each of them.

Import All Components in the App.js file and write the following code.

import React from "react";
import About from "./components/About";
import Home from "./components/Home";
import Login from "./components/Login";
import Signup from "./components/Signup";


const App = () =>{
  return (
    <div style={{height:"100vh",display:"flex", justifyContent:"center",alignItems:"center", flexDirection:"column"}}>
      <Home />
      <About />
      <Login />
      <Signup />
    </div>
  )
}

export default App;

Here I have written some styling to make content center in the screen and gave directions to the column to show it one by one.

Now I want to show every component based on their URL, See the below Example.

  1. localhost:3001 - Home Component

  2. localhost:3001/about - About Component

  3. localhost:3001/login - Login Component

  4. localhost:3001/signup - Signup Component

In order to create a route, based on the specific path we will create another component to make it reusable.

Create a directory in src and name it routes and inside routes create a file and name it Route.jsx.

Inside Route.jsx file write the following code.

import React, {useState, useEffect } from "react";

const Route = ({path,children}) =>{
    const [pathName,setPathName] = useState(window.location.pathname);
    useEffect(()=>{
        const onLocationChange = () =>{
            setPathName(window.location.pathname);
        }
        window.addEventListener("popstate",onLocationChange);

        return () =>{
            window.addEventListener("popstate",onLocationChange);
        };
    },[]);

    return pathName===path ?
    children : null;
}

export default Route;

We will need the state to store the current URL and useEffect to detect changes in routes.

Let's understand the code line by line.

Here we have imported useState and useEffect. Our component expects two props, one is the path and another one is children. children mean whatever we write inside Route tag it will be the children of Route, In our case, we will write the components inside Route.

window.location.pathname will give us the current path of the window. In order to listen to the route change, There is an event of javascript called popstate.

popstate event fired when any changes happen in window history, It is derived from window interface of the browser, For more information about popstate click here.

We will call onLocationChange function when route change and we will set the current path in our state and we will return children which will match the current route.

In the clean-up function of useEffect we have removed the event otherwise still it will listen to the event even though we will be not using it. So it will remove the event when route component will unmount.

Now import the Route component in App.js and wrap all components inside the route component. Follow the code below.

import React from "react";
import About from "./components/About";
import Home from "./components/Home";
import Login from "./components/Login";
import Signup from "./components/Signup";
import Route from "./route/Route";


const App = () =>{
  return (
    <div style={{height:"100vh",display:"flex", justifyContent:"center",alignItems:"center", flexDirection:"column"}}>
      <Route path="/">
          <Home />
      </Route>

      <Route path="/about">
        <About />
      </Route>

      <Route path="/login">
         <Login />
      </Route>

      <Route path="/signup">
          <Signup />
      </Route>      
    </div>
  )
}

export default App;

Here we have to provide the path to our route component, Also whatever component is wrapped inside the route component will be our children so if the path is localhost:3001/about then About component will be shown.

You can check the result by writing any route in the browser URL. localhost:3001/login will show the login component.

So this was just basic functionality to navigate based on route but now we need some menu/links to navigate without refreshing the page.

So we will create a new Component and we will call it Link, This component will return an anchor tag, So inside the route folder create a Component with the name of Link.jsx

Code of this component will be easy, See the below code.

import React from "react";

const Link = ({href,className,children}) =>{
    const onClick = event =>{
        event.preventDefault();

        window.history.pushState({},"",href);

        const navEvent = new PopStateEvent('popstate');
        window.dispatchEvent(navEvent);
    }
    return <a className={className} onClick={onClick}>{children}</a>
}
export default Link;

Here our component expects the href, whatever className you want to give for styling and the children which will be just the name of component or menu item.

As you can see above code we are returning the anchor tag but not using href attribute because it cause a page refresh due to server-side rendering.

Instead of href we will use click event and we will replace the URL. So whenever we want to replace the URL window has a history object which will take care of it.

Also, we are dispatching our popstate event and if you remember it, We are listening to this event in our Route.jsx component so as soon as popstate event is dispatched Route.jsx will perform an action accordingly.

Click here to learn more about the history object.

Here pushState method will be responsible to replace the URL. It expects three arguments, One is the state object, the second one is the title and the third one is the URL which we want to replace with the current URL.

We only need a third one which is URL, we will get the href in our component we will append the href in our base URL, So whatever the path after base URL, It will be replaced with the given href.

Now let's use it, We will create another component and we'll call it Header.jsx, The code will be very simple for this component.

Header.jsx

import React from "react";
import Link from "./Link";

const Header = () =>{
    return (
        <div className="ui secondary pointing menu">
            <Link href="/" className="item">Home</Link>
            <Link href="/about" className="item">About</Link>
            <Link href="/login" className="item">Login</Link>
            <Link href="/signup" className="item">Signup</Link>
        </div>
    )
}

export default Header;

So we will have to import Link component and we have to provide href, className is optional, and inside Link tag you can use any name. It will be the children of Link Component.

Now we will import the header component into our App.js so we can see the menu/links.

Now you can give some styling to the link like cursor should be a pointer and some margin, padding.

Finally, if you click on any link you will see the component accordingly and you also can see the URL will be changed without refreshing the page.

Congratulations, We have made our own small library to navigate among all components.

On a side note we should use react-router-dom library to use navigation. This is just for learning purpose.

If you find this useful then like this article and if you have any doubt you can use the comment box to ask the question.

Thanks :)