--- title: Address Book order: 2 --- # Address Book [MODES: framework]

We'll be building a small, but feature-rich address book app that lets you keep track of your contacts. There's no database or other "production ready" things, so we can stay focused on the features React Router gives you. We expect it to take 30-45m if you're following along, otherwise it's a quick read. You can also watch our [walkthrough of the React Router Tutorial](https://www.youtube.com/watch?v=pw8FAg07kdo) if you prefer 🎥 👉 **Every time you see this it means you need to do something in the app!** The rest is just there for your information and deeper understanding. Let's get to it. ## Setup 👉 **Generate a basic template** ```shellscript nonumber npx create-react-router@latest --template remix-run/react-router/tutorials/address-book ``` This uses a pretty bare-bones template but includes our css and data model, so we can focus on React Router. 👉 **Start the app** ```shellscript nonumber # cd into the app directory cd {wherever you put the app} # install dependencies if you haven't already npm install # start the server npm run dev ``` You should now be able to open up [http://localhost:5173][http-localhost-5173] and see your app running, though there's not much going on just yet. ## The Root Route Note the file at `app/root.tsx`. This is what we call the ["Root Route"][root-route]. It's the first component in the UI that renders, so it typically contains the global layout for the page, as well as a the default [Error Boundary][error-boundaries].
Expand here to see the root component code ```tsx filename=app/root.tsx import { Form, Scripts, ScrollRestoration, isRouteErrorResponse, } from "react-router"; import type { Route } from "./+types/root"; import appStylesHref from "./app.css?url"; export default function App() { return ( <>
## The Contact Route UI If you click on one of the sidebar items you'll get the default 404 page. Let's create a route that matches the url `/contacts/1`. 👉 **Create a contact route module** ```shellscript nonumber mkdir app/routes touch app/routes/contact.tsx ``` We could put this file anywhere we want, but to make things a bit more organized, we'll put all our routes inside the `app/routes` directory. You can also use [file-based routing if you prefer][file-route-conventions]. 👉 **Configure the route** We need to tell React Router about our new route. `routes.ts` is a special file where we can configure all our routes. ```tsx filename=routes.ts lines=[2,5] import type { RouteConfig } from "@react-router/dev/routes"; import { route } from "@react-router/dev/routes"; export default [ route("contacts/:contactId", "routes/contact.tsx"), ] satisfies RouteConfig; ``` In React Router, `:` makes a segment dynamic. We just made the following urls match the `routes/contact.tsx` route module: - `/contacts/123` - `/contacts/abc` 👉 **Add the contact component UI** It's just a bunch of elements, feel free to copy/paste. ```tsx filename=app/routes/contact.tsx import { Form } from "react-router"; import type { ContactRecord } from "../data"; export default function Contact() { const contact = { first: "Your", last: "Name", avatar: "https://placecats.com/200/200", twitter: "your_handle", notes: "Some notes", favorite: true, }; return (
{`${contact.first}

{contact.first || contact.last ? ( <> {contact.first} {contact.last} ) : ( No Name )}

{contact.twitter ? (

{contact.twitter}

) : null} {contact.notes ?

{contact.notes}

: null}
{ const response = confirm( "Please confirm you want to delete this record.", ); if (!response) { event.preventDefault(); } }} >
); } function Favorite({ contact, }: { contact: Pick; }) { const favorite = contact.favorite; return (
); } ``` Now if we click one of the links or visit [`/contacts/1`][contacts-1] we get ... nothing new? ## Nested Routes and Outlets React Router supports nested routing. In order for child routes to render inside of parent layouts, we need to render an [`Outlet`][outlet-component] in the parent. Let's fix it, open up `app/root.tsx` and render an outlet inside. 👉 **Render an [``][outlet-component]** ```tsx filename=app/root.tsx lines=[3,15-17] import { Form, Outlet, Scripts, ScrollRestoration, isRouteErrorResponse, } from "react-router"; // existing imports & exports export default function App() { return ( <>
); } ``` Now the child route should be rendering through the outlet. ## Client Side Routing You may or may not have noticed, but when we click the links in the sidebar, the browser is doing a full document request for the next URL instead of client side routing, which completely remounts our app. Client side routing allows our app to update the URL without reloading the entire page. Instead, the app can immediately render new UI. Let's make it happen with [``][link-component]. 👉 **Change the sidebar `` to ``** ```tsx filename=app/root.tsx lines=[3,20,23] import { Form, Link, Outlet, Scripts, ScrollRestoration, isRouteErrorResponse, } from "react-router"; // existing imports & exports export default function App() { return ( <> {/* other elements */} ); } ``` You can open the network tab in the browser devtools to see that it's not requesting documents anymore. ## Loading Data URL segments, layouts, and data are more often than not coupled (tripled?) together. We can see it in this app already: | URL Segment | Component | Data | | ------------------- | ----------- | ------------------ | | / | `` | list of contacts | | contacts/:contactId | `` | individual contact | Because of this natural coupling, React Router has data conventions to get data into your route components easily. First we'll create and export a [`clientLoader`][client-loader] function in the root route and then render the data. 👉 **Export a `clientLoader` function from `app/root.tsx` and render the data** The following code has a type error in it, we'll fix it in the next section ```tsx filename=app/root.tsx lines=[2,6-9,11-12,19-42] // existing imports import { getContacts } from "./data"; // existing exports export async function clientLoader() { const contacts = await getContacts(); return { contacts }; } export default function App({ loaderData }) { const { contacts } = loaderData; return ( <> {/* other elements */} ); } ``` That's it! React Router will now automatically keep that data in sync with your UI. The sidebar should now look like this: You may be wondering why we're "client" loading data instead of loading the data on the server so we can do server-side rendering (SSR). Right now our contacts site is a [Single Page App][spa], so there's no server-side rendering. This makes it really easy to deploy to any static hosting provider, but we'll talk more about how to enable SSR in a bit so you can learn about all the different [rendering strategies][rendering-strategies] React Router offers. ## Type Safety You probably noticed that we didn't assign a type to the `loaderData` prop. Let's fix that. 👉 **Add the `ComponentProps` type to the `App` component** ```tsx filename=app/root.tsx lines=[5-7] // existing imports import type { Route } from "./+types/root"; // existing imports & exports export default function App({ loaderData, }: Route.ComponentProps) { const { contacts } = loaderData; // existing code } ``` Wait, what? Where did these types come from?! We didn't define them, yet somehow they already know about the `contacts` property we returned from our `clientLoader`. That's because React Router [generates types for each route in your app][type-safety] to provide automatic type safety. ## Adding a `HydrateFallback` We mentioned earlier that we are working on a [Single Page App][spa] with no server-side rendering. If you look inside of [`react-router.config.ts`][react-router-config] you'll see that this is configured with a simple boolean: ```tsx filename=react-router.config.ts lines=[4] import { type Config } from "@react-router/dev/config"; export default { ssr: false, } satisfies Config; ``` You might have started noticing that whenever you refresh the page you get a flash of white before the app loads. Since we're only rendering on the client, there's nothing to show the user while the app is loading. 👉 **Add a `HydrateFallback` export** We can provide a fallback that will show up before the app is hydrated (rendering on the client for the first time) with a [`HydrateFallback`][hydrate-fallback] export. ```tsx filename=app/root.tsx lines=[3-10] // existing imports & exports export function HydrateFallback() { return (

Loading, please wait...

); } ``` Now if you refresh the page, you'll briefly see the loading splash before the app is hydrated. ## Index Routes When you load the app and aren't yet on a contact page, you'll notice a big blank page on the right side of the list. When a route has children, and you're at the parent route's path, the `` has nothing to render because no children match. You can think of [index routes][index-route] as the default child route to fill in that space. 👉 **Create an index route for the root route** ```shellscript nonumber touch app/routes/home.tsx ``` ```ts filename=app/routes.ts lines=[2,5] import type { RouteConfig } from "@react-router/dev/routes"; import { index, route } from "@react-router/dev/routes"; export default [ index("routes/home.tsx"), route("contacts/:contactId", "routes/contact.tsx"), ] satisfies RouteConfig; ``` 👉 **Fill in the index component's elements** Feel free to copy/paste, nothing special here. ```tsx filename=app/routes/home.tsx export default function Home() { return (

This is a demo for React Router.
Check out{" "}
the docs at reactrouter.com .

); } ``` Voilà! No more blank space. It's common to put dashboards, stats, feeds, etc. at index routes. They can participate in data loading as well. ## Adding an About Route Before we move on to working with dynamic data that the user can interact with, let's add a page with static content we expect to rarely change. An about page will be perfect for this. 👉 **Create the about route** ```shellscript nonumber touch app/routes/about.tsx ``` Don't forget to add the route to `app/routes.ts`: ```tsx filename=app/routes.ts lines=[4] export default [ index("routes/home.tsx"), route("contacts/:contactId", "routes/contact.tsx"), route("about", "routes/about.tsx"), ] satisfies RouteConfig; ``` 👉 **Add the about page UI** Nothing too special here, just copy and paste: ```tsx filename=app/routes/about.tsx import { Link } from "react-router"; export default function About() { return (
← Go to demo

About React Router Contacts

This is a demo application showing off some of the powerful features of React Router, including dynamic routing, nested routes, loaders, actions, and more.

Features

Explore the demo to see how React Router handles:

  • Data loading and mutations with loaders and actions
  • Nested routing with parent/child relationships
  • URL-based routing with dynamic segments
  • Pending and optimistic UI

Learn More

Check out the official documentation at{" "} reactrouter.com {" "} to learn more about building great web applications with React Router.

); } ``` 👉 **Add a link to the about page in the sidebar** ```tsx filename=app/root.tsx lines=[5-7] export default function App() { return ( <> {/* other elements */} ); } ``` Now navigate to the [about page][about-page] and it should look like this: ## Layout Routes We don't actually want the about page to be nested inside of the sidebar layout. Let's move the sidebar to a layout so we can avoid rendering it on the about page. Additionally, we want to avoid loading all the contacts data on the about page. 👉 **Create a layout route for the sidebar** You can name and put this layout route wherever you want, but putting it inside of a `layouts` directory will help keep things organized for our simple app. ```shellscript nonumber mkdir app/layouts touch app/layouts/sidebar.tsx ``` For now just return an [``][outlet-component]. ```tsx filename=app/layouts/sidebar.tsx import { Outlet } from "react-router"; export default function SidebarLayout() { return ; } ``` 👉 **Move route definitions under the sidebar layout** We can define a `layout` route to automatically render the sidebar for all matched routes within it. This is basically what our `root` was, but now we can scope it to specific routes. ```ts filename=app/routes.ts lines=[4,9,12] import type { RouteConfig } from "@react-router/dev/routes"; import { index, layout, route, } from "@react-router/dev/routes"; export default [ layout("layouts/sidebar.tsx", [ index("routes/home.tsx"), route("contacts/:contactId", "routes/contact.tsx"), ]), route("about", "routes/about.tsx"), ] satisfies RouteConfig; ``` 👉 **Move the layout and data fetching to the sidebar layout** We want to move the `clientLoader` and everything inside the `App` component to the sidebar layout. It should look like this: ```tsx filename=app/layouts/sidebar.tsx import { Form, Link, Outlet } from "react-router"; import { getContacts } from "../data"; import type { Route } from "./+types/sidebar"; export async function clientLoader() { const contacts = await getContacts(); return { contacts }; } export default function SidebarLayout({ loaderData, }: Route.ComponentProps) { const { contacts } = loaderData; return ( <>