React Router V6 - Must Know Knowledge
In this post you will learn what is React Router and how you can use it inside React correctly.
Actually React is considered a library and now a framework this is why we don't have a router inside React at all. This means that we must install some additional library and the most popular solution for React which is not official is called React Router.
As you can see this is the official website with version 6. This router covers all needs that you will have. This is why it doesn't make any sense to look for alternative.
Basic routing
So first of all let's try to install react-router and configure some basic routes. And just for you to know this guide uses react-router v6. It is important because they break quite a lot from major version to major version.
Now we must install our new package
npm install react-router-dom
As you can see we don't install react-router
but react-router-dom
. We write like this because inside react-router we have quite a lot of routes. As you can see here we have browser-router, hash-router, react-native-router and much more.
We install a router which is specific for web applications.
Our next step is to wrap our application with a router component.
import { BrowserRouter } from "react-router-dom";
ReactDOM.render(
<React.StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>,
document.getElementById("root")
);
With this BrowserRouter
we will get access to the router inside all our components.
Here I already create 3 components so we can render them on different pages.
// src/Dashboard.js
const Dashboard = () => {
return <div>Dashboard</div>;
};
export default Dashboard;
// src/Public.js
const Public = () => {
return <div>Public</div>;
};
export default Public;
// src/Private.js
const Private = () => {
return <div>Private</div>;
};
export default Private;
These are just components with some text.
Our next step here is to register some links and routes. Actually routes inside react-router is not registered like routes in some backend framework. For example in backend frameworks you have a file routes
and inside you define all possible routes for your application. Previously it worked exactly like this in react-router but not anymore.
Nowadays we define all our routes like components inside our React tree.
const App = () => {
return (
<div>
<h1>Hello monsterlessons</h1>
<Routes>
<Route path="/" element={<Dashboard />}>
</Route>
</Routes>
</div>
);
};
Here we defined a Routes
component with a Route
component inside. In it we must provide a path
and an element
.
Let's check if it's working.
As you can see on the home page our Dashboard
component was rendered.
Now let's render here one more route.
const App = () => {
return (
<div>
<h1>Hello monsterlessons</h1>
<Routes>
<Route path="/" element={<Dashboard />}>
<Route path="/protected" element={<Protected />}>
</Route>
</Routes>
</div>
);
};
Now we can jump to /protected
and see our component there.
Adding links
But obviously inside our application we want to jump to different routes. And for this we want to register links. For this we must use a component which is called Link
.
const App = () => {
return (
<div>
<h1>Hello monsterlessons</h1>
<Link to="/">Dashboard</Link>
<Link to="/protected">Protected</Link>
...
</div>
);
};
Inside a Link
we provide a url to to
property and a text of the link.
As you can see we rendered 2 links and we can jump between them without page reload and our component is being rerendered
Adding layout
Then next thing that you for sure want to implement is called layout. Typically you want to wrap all your routes with same layout on all pages. For example you have a header, footer and sidebar on all pages. How we can do that?
First of all let's create a layout component.
// src/Layout.js
import { Outlet } from "react-router-dom";
const Layout = () => {
return (
<div>
<h1>Layout</h1>
<div>
<Outlet />
</div>
</div>
);
};
export default Layout;
Here we imported an Outlet
component inside out layout. It is a placeholder where our component of the route will be rendered. So typically you create sidebar, header, footer in this Layout
component and every single route content will be rendered inside the Outlet
.
Now we must wrap all our routes with Layout
component.
<Routes>
<Route path="/" element={<Layout />}>
<Route index element={<Dashboard />} />
...
</Route>
</Routes>
So we just add a new Route
for homepage where we render Layout
component and all our routes we register as children routes inside of it.
But here is an important thing. Because we provided a path /
to our Layout
component we can't provide it to our Dashboard
that we want to render on the homepage. Instead we add an attribute index
to our Dashboard
route to define that it is our homepage.
As you can see in browser each of our routes is wrapped in Layout
component.
404 Page
The next important question is how to render 404 page for not existing routes. This is extremely easy to implement.
<Routes>
<Route path="/" element={<Layout />}>
...
<Route path="*" element={<div>404</div>} />
</Route>
</Routes>
Inside our Routes we register a new route with path *
. It means that for all not existing routes we will render this component. As you can see it is completely possible to render just markup in the element
even without creation of component.
If we try to open non existing route now our 404 markup is rendered.
Adding dynamic parameters
Another use case that you will have for sure is dynamic parameters. For example you have a list of articles that you need to render and you have a separate page for every single article where you render our article by slug.
In this case we must register a dynamic route.
<Routes>
<Route path="/" element={<Layout />}>
...
<Route path="/articles/:slug" element={<Article />}></Route>
</Route>
</Routes>
Here we have a dynamic url where we get a slug and render an Article
component. Now let's create article component and read our dynamic slug there.
import { useParams } from "react-router-dom";
const Article = () => {
const params = useParams();
console.log("params", params);
return <div>Article {params.slug}</div>;
};
export default Article;
As you can see we imported useParams
hook from react-router which allow us to render dynamic parameters from the url.
We successfully rendered a slug from url and can use it later to fetch article from API by a slug.
Getting query-params
Another common use case is to read query parameters from the url inside a page. For this we have a special hook.
import { useParams, useSearchParams } from "react-router-dom";
const Article = () => {
const params = useParams();
const [searchParams, setSearchParams] = useSearchParams();
console.log("params", params, searchParams.get("page"));
return <div>Article {params.slug}</div>;
};
export default Article;
Here we used useSearchParams
which allow us to get specific query params from the url. In this case here we have read page
value from the url /articles/foo?page=2
.
Programmatic navigation
One more thing that you will for sure need is programmatic navigation. It means that we want to trigger navigation to another route not by link but from our code.
Let's create a button which will navigate us to other route.
const App = () => {
const navigate = useNavigate();
return (
<div>
<h1>Hello monsterlessons</h1>
<button onClick={() => navigate('/protected')}>Go to protected</button>
...
</div>
);
};
Here we imported useNavigate
hook from react-router. Secondly we used it when we click on the button to jump to protected page. It is an extremely important feature if you want to redirect a user to other page after registration or authentication.
Authentication
Here is the last thing that I want to show you which is the most interesting. How we can protect our routes from being accessed by anonymous user?
Let's say that we want to prevent access to our /protected
page if we are not logged in. Typically you want to have a token we be authorized and make an API request but it is not related at all to react-router. Here I will just check if we have a token in local storage to give access to that page.
// src/Auth.js
import { Navigate } from "react-router-dom";
const Auth = ({ children }) => {
const token = localStorage.getItem("token");
if (!token) {
return <Navigate to="/" />;
}
return children;
};
export default Auth;
Here we created new Auth
component which will wrap our protected routes. It reads a token from local storage and redirects a user to homepage it he is not logged in. In other case it simply renders child route.
Now we need to wrap our protected route with Auth
.
<Routes>
<Route path="/" element={<Layout />}>
<Route
path="/protected"
element={
<Auth>
<Protected />
</Auth>
}
/>
</Route>
...
</Routes>
Here we simply wrapped our element
that we render with Auth
component.
As you can see in browser we can't access protected routes anymore because without token we are redirected to the home page. But if we set any value inside token
key then we can access it.
Want to conquer your next JavaScript interview? Download my FREE PDF - Pass Your JS Interview with Confidence and start preparing for success today!
📚 Source code of what we've done