DEV Community

Cover image for React Router DOM V6 Tutorial for Everyone
Vinit Gupta
Vinit Gupta

Posted on

React Router DOM V6 Tutorial for Everyone

For a Front-end application, one of the most important step is to define the routes and the pages that need to be shown to the user for the same.

The Old Way

Ages ago, when React router was not introduced, developers used window.location property to set the component to be displayed based on the conditions.

But, conflicted with the Thinking in React. React was introduced to prevent unnecessary page reloads.

For using the location property, the whole page had to be reloaded.

import React from "react"

import Login from "./components/Login"
import Profile from "./components/Profile"
import Home from "./components/Home"
import Dashboard from "./components/Dashboard"

const showHome = () => {
  if (window.location.pathname === "/") {
    return <Home />
  }
}

const showLogin = () => {
  if (window.location.pathname === "/login") {
    return <Login />
  }
}

const showDashboard = () => {
  if (window.location.pathname === "/dashboard") {
    return <Dashboard />
  }
}

const showProfile = () => {
  if (window.location.pathname === "/profile") {
    return <Profile />
  }
}

export default () => {
  return (
    <div className="ui container">
      {showHome()}
      {showLogin()}
      {showDashboard()}
      {showProfile()}
    </div>
  )
}

Enter fullscreen mode Exit fullscreen mode

React Router DOM

React Router DOM makes routing a lot easier and more intuitive.

For the above application, let's convert it to use React Router DOM.

If you have a React application, just install react-router-dom using the command :

npm install react-router-dom
Enter fullscreen mode Exit fullscreen mode

Once installed you are ready to use React Router in your application.

Select the location from where you want to enable routing. This is because there are certain components that are visible in all pages like the Navbar, Footer, etc.

Let's say you have a <Navbar /> and <Footer/> that stays on the screen on every page.

We can add the routing between these 2 components so that this is achieved :

<div className='app'>
      <Navbar />
      // Routing to be Added Here
      <Footer />
    </div>
Enter fullscreen mode Exit fullscreen mode

createBrowserRouter

React router exposes this Router function to configure the routes before adding it to your application.

createBrowserRouter takes in an array of route configs that is used for matching routes to the components to be displayed.

Each route config takes in a few parameters like :

  • path : this is the actual path that will be used for matching and displaying of components to the user
  • element : The Component to be shown on the screen based on the path defined above > 💡 The above 2 are Mandatory arguments, but there are some additional arguments (which we will know in detail later in this article)
  • errorElement : This is the fallback component to be displayed to user in case of errors or Exceptions
  • loader : Each route can define a "loader" function to provide data to the route element before it renders.

Let's see a basic example of creating a browser router :

const router = createBrowserRouter([
  {
    path: "/",
    element: <Home />
  },
  {
    path: "/login",
    element: <Login />,
  },
  {
    path: "/profile",
    element: <Profile />,
  },
  {
    path: "/dashboard",
    element: <Dashboard />,
  },
  {
    path: "*",
    element: <NotFound />,
  },
])
Enter fullscreen mode Exit fullscreen mode

This is how you define routes and the respective elements to be displayed.

🚨 Notice the * route - This is used to define the Custom Page to be displayed(in this case <NotFound /> for routes that are not defined instead of a Blank page to be shown to the User.

Now that the router is created, we need to tell React to use it for route matching.

RouterProvider Component

React Router DOM provides a RouterProvider component that is used to tell React to use the Router created above to display components at a certain location.

In our case, we are showing the Navbar and the Footer with all routes, so we add the <RouterProvider /> component in-between them.

The Router Provider takes in a router object as an argument to match the routes. So, we will pass the one we created above.

The code for the same :

function App() {


  return (
    <div className='app'>
      <Navbar />
      <RouterProvider router={router} />
      <Footer />
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

Here's the Demo 👇🏻

React Router Sample

Protected or Authenticated Routes

Now that we have defined the different routes, we can Protect them based on User Authentication and Authorization.

We create a Custom Authentication Provider to wrap our whole Application.
Here, we mock the different cases based on Inputs :

  • Setting if the user is signed in.
  • The Role of the User : ADMIN or USER
import { createContext, PropsWithChildren, useContext, useState } from 'react';
export enum UserType {
  USER,
  ADMIN
}
interface User {
    id : number,
    name : string,
    role : UserType
}

const AuthContext = createContext<User | null>(null);

type AuthProviderProps = PropsWithChildren & {
  isSignedIn?: boolean;
  role : UserType
};

export default function AuthProvider({
  children,
  isSignedIn,
  role
}: AuthProviderProps) {
  // Uses `isSignedIn` prop to determine whether or not to render a user
  const [user] = useState<User | null>(isSignedIn ? { id: 1, name : "Vinit", role : role } : null);

  return <AuthContext.Provider value={user}>{children}</AuthContext.Provider>;
}

export const useAuth = () => {
  const context = useContext(AuthContext);

  if (context === undefined) {
    throw new Error('useAuth must be used within an AuthProvider');
  }

  return context;
};

Enter fullscreen mode Exit fullscreen mode

NOTE : This is a Sample Custom Context Hook creation code. Check out this Amazing Blog by @miracool on how to use Authentication Context in React.

Now we can create a <ProtectedRoute/> component that checks if a User is Logged In. If not, redirects to /login route.

function ProtectedRoute({children} : PropsWithChildren) {
    const user = useAuth();
    const navigate = useNavigate();

    useEffect(()=> {
        if(user===null) {
            navigate("/login");
        }
    }, [user, navigate])
  return <>{children}</>;
}
Enter fullscreen mode Exit fullscreen mode

I want the /profile and /dashboard Route to be accessible only if a User is Logged In. So I wrap these 2 with the Protected Route component inside the createBrowserRouter config :

{
    path: "/profile",
    element: <ProtectedRoute><Profile /></ProtectedRoute>,
  },
  {
    path: "/dashboard",
    element: <ProtectedRoute><Dashboard /></ProtectedRoute>,
  },
Enter fullscreen mode Exit fullscreen mode

And voila, these 2 routes are only accessible to Logged In users.

But, we can go A STEP FURTHER with the /dashboard route and make it only accessible to ADMINS.

To do this, we create another component : <AdminRoute/> and check if the Logged In User is an Admin.

If Not, we redirect the user to a /contact-admin route, that Simply renders : Contact the Admin.

function AdminRoute({children} : PropsWithChildren) {
    const user = useAuth();
    const navigate = useNavigate();

    useEffect(()=> {
        if(user?.role!=UserType.ADMIN) {
            navigate("/contact-admin");
        }
    }, [user, navigate])
  return (
    <div>{children}</div>
  )
}
Enter fullscreen mode Exit fullscreen mode

To make things more intuitive, we make the Protected Routes nested :

{
    path: "/dashboard",
    element: <ProtectedRoute><AdminRoute><Dashboard /></AdminRoute></ProtectedRoute>,
  },
  {
    path: "/contact-admin",
    element: <ContactAdmin />,
  },
Enter fullscreen mode Exit fullscreen mode

Handling Errors 🚨

We are Humans. We do Mistakes.
There might be cases when there is an issue while Rendering a Particular Component.

At that point, we don't want the User to feel something like this :
React Router DOM

This is where the errorElement argument in the Router Config object comes in.

If there is an Error in Rendering that particular Component, React router displays the Component you passed in.

So when I add the following in my <Home/> component :

function Home()
    throw new Error("Something")
}
Enter fullscreen mode Exit fullscreen mode

I get the following page instead of Random Red Screens(also you can see the path if you follow the Arrow ↖) :

Error Handling in React Router DOM

The code for the Config Object looks like this :

{
    path: "/",
    element: <Home />,
    errorElement : <Error/>
  },
Enter fullscreen mode Exit fullscreen mode

👉🏻 You can add Custom Error Components for each of the Routes as per your Choice

Loading Data for Component

You might have to fetch some data to be used in the Component.
The best way to do is to Put the Logic the Component the Data is Needed.

But let's say there is some data fetching needed before the Component is Loaded.
This is where the loader argument comes in.

loader takes in a function that returns some data. Once the data is fetched, the component is loaded along with that data accessible using the useLoaderData() hook.

Let's say a User has some posts that need to be fetched from a server(I am using the Infamous Json PlaceHolder API to load some fake posts.

This is really simple to achieve using React Router.

The first step is to define a Utility Function to fetch the Data :

const fetchPosts = async () => {
  try {
    const posts = await fetch("https://jsonplaceholder.typicode.com/posts/");
    return posts
  } catch (error) {
    console.log(error)
    return  []
  }
}
Enter fullscreen mode Exit fullscreen mode

And pass this function into the loader argument of the Route Config object :

{
    path: "/profile",
    element: <ProtectedRoute><Profile /></ProtectedRoute>,
    loader : fetchPosts
  },
Enter fullscreen mode Exit fullscreen mode

Whenever the /profile route is hit in this case, the fetchPosts function is called and the Data is Parsed as a JSON.

Now, to use this inside my Profile component, all I have to do is use the useLoaderData() hook provided by React Router DOM and use it in my component :

const posts = useLoaderData() as Array<any>;
  return (
    <div>
      <div className='navbar'>
        <Link to={"/"} className='link mx'>Home</Link>
        <Link to={"/profile"} className='link mx'>Profile</Link>
        <Link to={"/dashboard"} className='link mx'>Dashboard</Link>
    </div>
      <h2>Your Posts</h2>
      {
        posts && posts.slice(0,10).map((post) => {
          return <div className='link'>{post.title}</div>
        })
      }
      </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

With React Router, you can do a lot of Customizations and achieve various Functionalities if you dive deeper, but this is the Gist of the Most of Important functionalities for React Router DOM.

Do check out their official Documentation for more explanations and guides.

And yes, do share if you like this Guide.

Top comments (0)