React Router DOM V6 Tutorial for Everyone
Tags: webdev, react, codenewbie, tutorial
Published: 2024-04-21 14:32:39

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. 

```js
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>
  )
}

```

## 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 : 

```js
npm install react-router-dom
```

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 : 

```js
<div className='app'>
      <Navbar />
      // Routing to be Added Here
      <Footer />
    </div>
```

### `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 : 
```js
const router = createBrowserRouter([
  {
    path: "/",
    element: <Home />
  },
  {
    path: "/login",
    element: <Login />,
  },
  {
    path: "/profile",
    element: <Profile />,
  },
  {
    path: "/dashboard",
    element: <Dashboard />,
  },
  {
    path: "*",
    element: <NotFound />,
  },
])
```

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 : 
```js
function App() {


  return (
    <div className='app'>
      <Navbar />
      <RouterProvider router={router} />
      <Footer />
    </div>
  )
}
```

### Here's the Demo 👇🏻

![React Router Sample](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/66ilqgz60bf50gysiygl.gif)


## 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`

```ts
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;
};

```

> NOTE : This is a Sample Custom Context Hook creation code. Check out this Amazing [Blog](https://dev.to/miracool/how-to-manage-user-authentication-with-react-js-3ic5) 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. 


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

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

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 : 

```js
{
    path: "/profile",
    element: <ProtectedRoute><Profile /></ProtectedRoute>,
  },
  {
    path: "/dashboard",
    element: <ProtectedRoute><Dashboard /></ProtectedRoute>,
  },
```

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`.

```js
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>
  )
}
```

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

```js
{
    path: "/dashboard",
    element: <ProtectedRoute><AdminRoute><Dashboard /></AdminRoute></ProtectedRoute>,
  },
  {
    path: "/contact-admin",
    element: <ContactAdmin />,
  },
```

## 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 : 
<img src="https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExeXplNHBrY20wOTc2YnBtNGNiczhpNTQzemJ5OWYyamUwZmFqamE5OSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/sE1fbQPozKg3q5I2W2/giphy.gif" alt="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 : 
```js
function Home()
    throw new Error("Something")
}
```

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tnrsgorcfxqdxqgz50ks.png)


The code for the Config Object looks like this : 
```js
{
    path: "/",
    element: <Home />,
    errorElement : <Error/>
  },
```

> ### 👉🏻 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 : 

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

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

```js
{
    path: "/profile",
    element: <ProtectedRoute><Profile /></ProtectedRoute>,
    loader : fetchPosts
  },
```

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 : 

```js
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>
  )
}
```

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](https://reactrouter.com/en/main) for more explanations and guides.

And yes, do share if you like this Guide.