Welcome, Developer πŸ‘‹ How have you been? If you are a first-timer, be very welcome to our blog! For those who have been here before, hope you have learned something cool so far.

Keen for a walk-through video of this post? Check it out on YouTube, developer πŸ˜€

Every time more, I do my best to invest time and effort to the Welcome, Developer ecosystem. On the last week, I published the first video to our YouTube channel πŸŽ‰ It presents a quick and practical approach for getting started with React Query. If you are interested in learning more about managing cache on your React apps, check it out as well as the post about the subject here in the blog.

For today, I’ve prepared a fun journey for us to go through together. It’s one of the most important knowledges (IMO) to have when developing React applications (after React itself, of course πŸ˜…). It’s about the React Router library.

Done with all the talking, time to get our hands dirty, developer! Grab your favourite beverage (no judgement) and let’s get started, shall we?

Introduction

What is React Router? It’s a client and server-side routing library for React (not my words!). Got it, but hold on… what is routing, please? Well, routing is the mechanism by which page requests are redirected to the component or code that handles them. Simply put, it’s how React knows what UI components to present based on a route.

In the following chapters, we'll learn how to become fluent in the React Router library through hands-on exercises and step-by-step explanations πŸ€“

I hope you enjoy the ride, developer πŸ€—

Before We Start

Before we get started, let's create our React app. I’m going to use Vite with npm, feel free to use your favourite one though (CRA, yarn):

npm init vite@latest my-react-router-app -- --template react-ts # vite # or npx create-react-app my-react-router-app --template typescript # npx

Next, let’s install the dependencies and make sure the app is running successfully:

cd my-react-router-app npm i # or `npm install` npm run dev # or `npm start` for CRA

Awesome, that’s all we need before starting, developer πŸ‘

Installing React Router

Now that we have just created our React app, let’s install the React Router library:

npm i react-router-dom@6

Note that we install the version 6 (six) of the library, which is the latest one.

Tidying Up Boilerplate Code

To start with a green field, let’s get ride of everything that comes with the Vite boilerplate and we don't actually need.

First, on src/App.tsx, replace the existing code by the one below:

src/App.tsx
import "./App.css"; function App() { return ( <div className="App"> <header className="App-header">Welcome, Developer πŸ‘‹</header> <main className="App-content"> This is the Home page πŸͺ </main> </div> ); } export default App;

Next, a little tweak to the src/App.css CSS file, as highlighted below:

src/App.css
.App-header { background-color: #282c34; min-height: 30vh; display: flex; flex-direction: column; align-items: center; justify-content: center; font-size: calc(10px + 2vmin); color: white; } .App-content { background-color: #1e1e1e; min-height: 70vh; display: flex; flex-direction: column; align-items: center; justify-content: center; font-size: calc(10px + 2vmin); color: #458cf1; }

That’s it. If you wanna remove unused files, such as logo.svg, it’s up to you.

Browser Router

First thing we need, before starting to work with the routing library, is to add the Browser Router to our entry point component. It basically consists of an interface that enables the React Router to run in the web browser. According to the documentation, "It stores the current location in the browser's address bar using clean URLs and navigates using the browser's built-in history stack.".

Let’s add the Browser Router to the entry point of our app. If you also created the app using Vite, then your file is the src/main.tsx, otherwise, it should be index.tsx:

import React from "react"; import ReactDOM from "react-dom/client"; import { BrowserRouter } from "react-router-dom"; import App from "./App"; import "./index.css"; ReactDOM.createRoot(document.getElementById("root")!).render( <React.StrictMode> <BrowserRouter> <App /> </BrowserRouter> </React.StrictMode> );

Now, we're ready to start using the routing libraryπŸ‘Œ

Routes

Well, you and I know that the main reason for having a routing library is to enable navigation amongst different pages or content, don't we? Good, we're on the same page. So, we must do our part of the deal and create the routes, while React Router does its part and provide us with the navigation. Does it sound fair, developer?

To put it simply, a route is nothing more than a path that React Router will use to determine what component to render. Could I create any path I want? Yes, you can. Should I? Well, as long as you follow an appropriate naming convention, you can πŸ™ƒ

A few examples of reasonable route paths I have in mind is 'src/pages', 'src/routes', 'src/you-name-it'. My favourite, and the one I chose for this tutorial, is src/routes. Yep, it's the same used on the React Router tutorial and also the one used by Remix, but we must agree that is the one that makes the most sense.

I thought about creating a couple of routes to illustrate our learning journey:

  1. Project
  2. About
  3. Contact

Create a new folder called src/routes below the src/ folder:

cd src mkdir routes

Let's create the first route together, then you do the same for the others:

  1. Inside the src/routes folder, create a new component called project:
cd routes echo > project.tsx
  1. Then, add the following code to the project.tsx component file:
src/routes/project.tsx
const Project = () => { return <h2>This is the Project page πŸ§‘β€πŸ’»</h2>; }; export default Project;

Piece of cake, right? I'll wait for you, take your time!

Done? Great, can we start using the routes then? Not so fast, developer πŸ˜‰ Our app does not have a single idea yet of the routes that we just created, and even less what to present in every each of them!

Let's solve this problem, shall we?

Registering Routes

On src/main.tsx, add the following code so that React Router can find the routes:

src/main.tsx
import React from "react"; import ReactDOM from "react-dom/client"; import { BrowserRouter, Route, Routes } from "react-router-dom"; import App from "./App"; import "./index.css"; import Project from "./routes/project"; import About from "./routes/about"; import Contact from "./routes/contact"; ReactDOM.createRoot(document.getElementById("root")!).render( <React.StrictMode> <BrowserRouter> <Routes> <Route path="/" element={<App />} /> <Route path="project" element={<Project />} /> <Route path="about" element={<About />} /> <Route path="contact" element={<Contact />} /> </Routes> </BrowserRouter> </React.StrictMode> );

Nice one, developer! The app knows what, and when, to render based on URL requests.

If you are keen to test it, just start the app and navigate to any of the routes we created by typing the path in the address bar of your browserπŸ‘

Links & Navigation

Last piece of the puzzle, let's learn how to navigate from one route to another (not manually, of course!). We're going to need links that redirect an user to different routes, so let's create it.

In case you are wondering if I'm talking about HTML <a> hyperlink tags, you are almost right! In the React Router's land, there is a Link component that can be used to create a link to a route. This component renders an accessible <a> element, with the same behavior in the browser that you would expect from that HTML element (right-click options, for example).

Let's quickly build a navigation component to use in our app. Then, we'll add a couple of links to it.

First things first, we must create a new folder called components inside the src folder:

cd src mkdir components

Next, create a new folder in src/components to store our navigation component, called Navigation:

cd components mkdir Navigation

Personal preference now, but I like to organize my React components following the approach very well described by Josh W. Comeau here πŸ‘ If you are like me, we'll need two files, an index.tsx and a Navigation.tsx file:

cd Navigation echo > index.tsx echo > Navigation.tsx

The index.tsx file is only responsible for exporting the component to the "world" (I mean, to the other components in the app). The Navigation.tsx file is the one that actually contains the UI component code. Have you found yourself with many index files opened through your VS Code tabs that made you feel lost? Well, you can now close them and open just the component files, with proper tab names that won't be confusing.

Apologies for the distraction, back to our Navigation component πŸ™ƒ Let's code it:

src/components/Navigation/Navigation.tsx
import { Link } from "react-router-dom"; export const Navigation = () => ( <div style={{ margin: "20px", }} > <nav> <Link to="/">Home</Link> {" | "} <Link to="/project">Projects</Link> {" | "} <Link to="/about">About</Link> {" | "} <Link to="/contact">Contact</Link> </nav> </div> );

See the Link component? Simple to use, isn't it? We just need to inform what is the route that the link redirects to, through the to property.

Then, we export it through the index file:

src/components/Navigation/index.tsx
export * from "./Navigation"; export { Navigation } from "./Navigation";

Perfect. Let's import the Navigation component to our App component:

src/App.tsx
import { Outlet } from "react-router-dom"; import "./App.css"; import { Navigation } from "./components/Navigation"; function App() { return ( <div className="App"> <header className="App-header"> <b>Welcome, Developer πŸ‘‹</b> <Navigation /> </header> <main className="App-content"> This is the Home page πŸͺ </main> </div> ); } export default App;

Shall we test it? Let's do it!

npm run dev

Are you able to navigate to the routes? Yes, you are! (hopefully) πŸ˜… do they all the share the same nice look? no, they do not. Stick with me, I can show you how!

✨ The Doors of Durin πŸ§™β€β™‚οΈ

Developer, let me tell you something you already know. At this point, you know enough of React Router to create a simple navigation logic that can take you to different routes in a React application. So you might ask "Is it all that the library provides?" The answer is no, definitely not!

React Router is a fantastic library and provides a lot more than what I just showed you.

In case you are interested in just the very basics of the library, you are good to go. However, if you have some time available and most importantly, if you are enjoying this journey, I invite you to bear with me so we can learn more advanced concepts! What do you say? The answer is a simple:

"Ennyn Durin Aran Moria. Pedo Mellon a Minno. Im Narvi hain echant. Celebrimbor o Eregion teithant i thiw hin." - Gandalf the Grey

If you are a fan of LOTR, like myself, I'm pretty sure you've got the reference πŸ˜‰ Let's continue our journey, my friend!

Layout Routes

If you have navigated to other routes, you saw that each of them has an unique look and feel. That's because they do not share anything in common, apart from being called by the same navigation component.

In a real world, often we want to have a layout shared across most of the routes. This common layout is usually composed of one or more UI components, such as one navigation menu, header, footer, company logo, etc. Do we want to re-write the layout on every route? Of course not, Ctrl+C and Ctrl+V isn't so popular sometimes, am I right?

The new version of React Router (6.0.3) introduced a new way of sharing layouts throughout the application (which is awesome, by the way!). The concept is called Layout Routes, and it consists of nested routes being rendered in a parent route. There are many cases when it is used, such as when sharing a common layout or when decoupling parts of the page to routes containing smaller UI components.

Now, you have to pay close attention to the code to see the subtle change we will make! Back to our main component:

src/main.tsx
import React from "react"; import ReactDOM from "react-dom/client"; import { BrowserRouter, Route, Routes } from "react-router-dom"; ​ import App from "./App"; import "./index.css"; ​ import Project from "./routes/project"; import About from "./routes/about"; import Contact from "./routes/contact"; ​ ReactDOM.createRoot(document.getElementById("root")!).render( <React.StrictMode> <BrowserRouter> <Routes> <Route path="/" element={<App />}> <Route path="project" element={<Project />} /> <Route path="about" element={<About />} /> <Route path="contact" element={<Contact />} /> </Route> </Routes> </BrowserRouter> </React.StrictMode> );

Be honest with me, developer...If I hadn't highlighted the code, would you be able to note the difference? Probably not, I guesss. Pretty cool, isn't?

Really, was that all? Well, almost πŸ‘Œ Let me give you one last tip. On the App component, we will introduce a new React Router component called Outlet. Allow me to quickly explain what it is, please. An Outlet component is used in a parent route to render child route elements. Does it make sense? Do you remember those layout components in React? Same thing pretty much.

"What if I have an Outlet on the parent route and I'm not sure if children routes will always be rendered?" No worries, it won't have any impacts to the parent route component.

Back to the App component, mate. Let's add an Outlet component, then?

src/App.tsx
import { Outlet } from "react-router-dom"; import "./App.css"; import { Navigation } from "./components/Navigation"; function App() { return ( <div className="App"> <header className="App-header"> <b>Welcome, Developer πŸ‘‹</b> <Navigation /> </header> <main className="App-content"> <Outlet /> </main> </div> ); } export default App;

Nice, please note that the App component isn't our Homepage anymore. We must create a route component to be a homepage instead. The process is the same as we did previously for the other routes (ie, Projects, etc.).

cd src cd routes echo > home.tsx

Then, add a simple content to it:

src/routes/home.tsx
const Home = () => { return <h2>This is the Home page πŸͺ</h2>; }; export default Home;

Lastly, a very small adjust to the main component:

src/main.tsx
import React from "react"; import ReactDOM from "react-dom/client"; import { BrowserRouter, Route, Routes } from "react-router-dom"; import App from "./App"; import "./index.css"; import Project from "./routes/project"; import About from "./routes/about"; import Contact from "./routes/contact"; import Home from "./routes/home"; ReactDOM.createRoot(document.getElementById("root")!).render( <React.StrictMode> <BrowserRouter> <Routes> <Route element={<App />}> <Route path="/" element={<Home />} /> <Route path="project" element={<Project />} /> <Route path="about" element={<About />} /> <Route path="contact" element={<Contact />} /> </Route> </Routes> </BrowserRouter> </React.StrictMode> );

Ready for the moment of truth, developer? Give it a test!

npm run dev

Use the navigation menu to switch between routes! How cool is that, huh? 😏

Nested routes? Not an unknown territory for us anymore!

Next, let me tell you how to add a "not found" route.

No Match Route

Well, we never know what routes (aka. URLs) the user will enter. So, we need to handle the scenarios when the user enters a wrong URL, resulting in a 404 HTTP error code.

Remember on the main.tsx that we added all the routes that we have planned? Let's get back there, and add a new route to the Routes collection:

src/main.tsx
import React from "react"; import ReactDOM from "react-dom/client"; import { BrowserRouter, Route, Routes } from "react-router-dom"; import App from "./App"; import "./index.css"; import Project from "./routes/project"; import About from "./routes/about"; import Contact from "./routes/contact"; import Home from "./routes/home"; import NotFound from "./routes/notFound"; ReactDOM.createRoot(document.getElementById("root")!).render( <React.StrictMode> <BrowserRouter> <Routes> <Route element={<App />}> <Route path="/" element={<Home />} /> <Route path="project" element={<Project />} /> <Route path="about" element={<About />} /> <Route path="contact" element={<Contact />} /> <Route path="*" element={<NotFound />} /> </Route> </Routes> </BrowserRouter> </React.StrictMode> );

Got the logic, developer? If the requested route isn't anything specifically defined (such as project, about, contact), it will be captured by the No Match route.

As you imagined, we must create a new route component called NotFound on the routes folder:

cd src cd routes echo > notFound.tsx

Then, add a simple content to it:

src/routes/notFound.tsx
const NotFound = () => { return <h2>No signal of intelligent life detected πŸ€–</h2>; }; export default NotFound;

Take the time to test it πŸ‘

npm run dev

Cool, one more lesson accomplished! Next, have you heard about Index routes? Allow me to tell you what it is and how to use it, please 😊

Index Routes

Follow along with me, developer. Imagine you have a list of nested routes, as we do have, and the parent route component has a Outlet component (same as our App component). Nothing new here so far, I know.

What if we want to render something in that Outlet space when the parent route itself is requested? Some sort of default content to be presented through the Outlet. Does that make sense?

For example, looking at our app, the main.tsx component currently has the following logic (no need to do anything):

src/main.tsx
import React from "react"; import ReactDOM from "react-dom/client"; import { BrowserRouter, Route, Routes } from "react-router-dom"; import App from "./App"; import "./index.css"; import Project from "./routes/project"; import About from "./routes/about"; import Contact from "./routes/contact"; import Home from "./routes/home"; import NotFound from "./routes/notFound"; ReactDOM.createRoot(document.getElementById("root")!).render( <React.StrictMode> <BrowserRouter> <Routes> <Route element={<App />}> <Route path="/" element={<Home />} /> <Route path="project" element={<Project />} /> <Route path="about" element={<About />} /> <Route path="contact" element={<Contact />} /> <Route path="*" element={<NotFound />} /> </Route> </Routes> </BrowserRouter> </React.StrictMode> );

What's the purpose of a Home route component? Should it not be rendered through the App component instead? That't when an Index route comes to the rescue! Let me tell you when to use an Index route, please:

  • "Index routes render in the parent routes outlet at the parent route's path."
  • "Index routes match when a parent route matches but none of the other children match."
  • "Index routes are the default child route for a parent route."
  • "Index routes render when the user hasn't clicked one of the items in a navigation list yet."

Let me admit something. The scenarios above aren't from my head. I copied them over from the official documentation. Sorry, I couldn't come up with more straightforward explanations!

So, how do we adjust our app to start using an Index route? Easy, let me guide you:

src/main.tsx
import React from "react"; import ReactDOM from "react-dom/client"; import { BrowserRouter, Route, Routes } from "react-router-dom"; import App from "./App"; import "./index.css"; import Project from "./routes/project"; import About from "./routes/about"; import Contact from "./routes/contact"; import Home from "./routes/home"; import NotFound from "./routes/notFound"; ReactDOM.createRoot(document.getElementById("root")!).render( <React.StrictMode> <BrowserRouter> <Routes> <Route path="/" element={<App />}> <Route index element={<Home />} /> <Route path="project" element={<Project />} /> <Route path="about" element={<About />} /> <Route path="contact" element={<Contact />} /> <Route path="*" element={<NotFound />} /> </Route> </Routes> </BrowserRouter> </React.StrictMode> );

Piece of cake, isn't it?

So many cool things yet to show you, developer! Hopefully, you aren't getting bored, are you? 🫠

Active Links

How to make a link to look active? How to show the user what route is active without this having to look at the browser's URL address? Well, spoiler alert, React Router has an easy way to do this!

If we want to change the style of a link, we must go to our Navigation component:

src/components/Navigation/Navigation.tsx
import { NavLink } from "react-router-dom"; export const Navigation = () => ( <div style={{ margin: "20px", }} > <nav> <NavLink style={({ isActive }) => { return { color: isActive ? "pink" : "", }; }} to="/" > Home </NavLink> {" | "} <NavLink style={({ isActive }) => { return { color: isActive ? "pink" : "", }; }} to="/project" > Projects </NavLink> {" | "} <NavLink style={({ isActive }) => { return { color: isActive ? "pink" : "", }; }} to="/about" > About </NavLink> {" | "} <NavLink style={({ isActive }) => { return { color: isActive ? "pink" : "", }; }} to="/contact" > Contact </NavLink> </nav> </div> );

Right, do you see that we are using a NavLink instead of a Link now? That's because we want to use the isActive prop to change the style of the link. You might be wondering: "Was the swap just because of the style thing?". Well, yes 😊 A NavLink is a special kind of Link that knows whether or not it is active, so we can use the isActive property to set different CSS styles.

I know, I know...those bunch of NavLinks could be a reusable component instead. But, let's stick to the purpose of this tutorial.

What is next? I saved the best for last, developer! If you are tired, take a break, get another coffee! I'll do the same, and meet you here β˜•

Reading URL Params

Question: What if we want to get the params from the URL? What for we would do that? Humm, imagine the following scenario.

We have a list of users. For each user, we want to create a route to be rendered in a parent route. This unique user-route should render its own content, based on its details. If the users list consists of thousands of users, should we create one route component for each user? Not viable, right?

What we want to do instead, is to create a single user-route component only and then render the content dynamically.

Shall we create a dataset of users to illustrate our goal? Let's create a new folder under src called data, then a new file called users.ts:

cd src mkdir data cd data echo > users.ts

Then, we can add some users to the dataset:

src/data/users.ts
export interface User { id: number; name: string; emoji: string; } const users: User[] = [ { id: 1, name: "Martin McFly", emoji: "πŸ§‘", }, { id: 2, name: "Lorraine Baines", emoji: "πŸ‘©", }, { id: 3, name: "Doc Brown", emoji: "πŸ‘΄πŸ»", }, { id: 4, name: "Einstein", emoji: "🐢", }, ]; export function getUsers(): User[] { return users; }

We just have defined an interface to ensure that our users are well-formed, and added a function to get the users.

Next, we want to create a link for each user in our homepage. So, whoever has landed on the app, can tell us who they are!

Let's create a new link for each user's route, setting its path according to their IDs:

src/routes/home.tsx
import { Link } from "react-router-dom"; import { getUsers, User } from "../data/users"; const Home = () => { const users = getUsers(); return ( <> <h2>Who are you, traveler? πŸ›Έ</h2> <p>My name is:</p> {users.map((user: User) => ( <Link to={`/users/${user.id}`} key={user.id}>{user.name}</Link> ))} <p>and I come in peace πŸ––</p> </> ); }; export default Home;

Perfect, we're getting there. Now, we must decide where to render the user information. What about creating a Users page? By "page", I mean a route and a component 😎

cd src cd routes echo > users.tsx

Then, add the basic content to the Users route component:

src/routes/users.tsx
import { Outlet } from "react-router-dom"; const Users = () => { return ( <> <h2>This is the Users page!</h2> <Outlet /> </> ); }; export default Users;

I'm using an Outlet component because I want to render user specific information in that space. Same as we did previously, correct?

Now, we need a route to handle and render only user information. Let's create a User route component:

cd src cd routes echo > user.tsx

Then, add the basic content to the User route component:

src/routes/user.tsx
import { useParams } from "react-router-dom"; const User = () => { const params = useParams(); return <h2>{`Hi, I'm ${params.userID}. I came from the future!`}</h2>; }; export default User;

We've got the route components that we need. Next, back to the main.tsx component, we register one route for the /users path and another one for the dynamic user IDs:

src/main.tsx
... import User from "./routes/user"; import Users from "./routes/users"; ReactDOM.createRoot(document.getElementById("root")!).render( <React.StrictMode> <BrowserRouter> <Routes> <Route path="/" element={<App />}> <Route index element={<Home />} /> <Route path="project" element={<Project />} /> <Route path="about" element={<About />} /> <Route path="contact" element={<Contact />} /> <Route path="users" element={<Users />}> <Route path=":userID" element={<User />} /> </Route> <Route path="*" element={<NotFound />} /> </Route> </Routes> </BrowserRouter> </React.StrictMode> );

By the way, order does matter. The Users route must be registered before the No Match route, otherwise, it will never match.

Shall we test it again?

npm run dev

Can you navigate to the user page, showing the right data? Oh okay, is it only showing the ID instead of the name? That's expected πŸ‘ Don't you worry, this is the last step I promise!

We need to do some magic to the User route component:

src/routes/user.tsx
import { useParams } from "react-router-dom"; import { getUsers } from "../data/users"; const User = () => { const params = useParams(); const users = getUsers(); const selectedUser = users.find( (user) => params.userID === user.id.toString() ); const name = selectedUser ? selectedUser.name : "(forgot my name)"; const picture = selectedUser ? selectedUser.emoji : "πŸ€”"; return <h2>{`Hi, I'm ${name} ${picture} I came from the future!`}</h2>; }; export default User;

You should be good to go now! Cool, isn't it? πŸ‘½

Next Steps

Question: Did we cover everything about the React Router library? Not at all.

Question: Did we cover enough for you to hit the ground running? Definitely πŸš€

What is next, then? Take the time to read the React Router documentation. It's worth it, believe me (and it isn't one of those boring documentations).

Resources

Conclusion

Whatever you stopped before, or went through the Doors of Durin, I admire your efforts πŸ‘Š

In this busy world we live in, finding the time to study and learn new skills isn't always easy, is it? I know, we're all on the same boat, my friend. Always remember this famous quote by Martin Luther King Jr.:

β€œIf you can't fly, run. If you can't run, walk. If you can't walk, crawl, but by all means, keep moving.”

I hope you enjoyed this journey, and have learned a lot about React Router πŸ’ͺ

Take care, stay safe, and see you in the next one, developer!

Β© 2022 Welcome, Developer. All rights reserved.