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>
)
}
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
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>
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 />,
},
])
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>
)
}
Here's the Demo 👇🏻
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
orUSER
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 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}</>;
}
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>,
},
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>
)
}
To make things more intuitive, we make the Protected Routes nested :
{
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 :
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")
}
I get the following page instead of Random Red Screens(also you can see the path if you follow the Arrow ↖) :
The code for the Config Object looks like this :
{
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 :
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 :
{
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 :
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 for more explanations and guides.
And yes, do share if you like this Guide.
Top comments (1)
Awesome guide. Thanks for taking the time to write this tutorials but do you have any project based tutorials where this awesome tutorial of yours can be leaent while working on the project. If you have or can find one, do let me share. Thanks