π **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
## Handling Not Found Errors
It's always a good idea to know how your app responds to errors early in the project because we all write far more bugs than features when building a new app! Not only will your users get a good experience when this happens, but it helps you during development as well.
We added some links to this app, let's see what happens when we click them?
π **Click one of the sidebar names**
Gross! This is the default error screen in React Router, made worse by our flex box styles on the root element in this app π.
Anytime your app throws an error while rendering, loading data, or performing data mutations, React Router will catch it and render an error screen. Let's make our own error page.
π **Create an error page component**
```sh
touch src/error-page.jsx
```
```jsx filename=src/error-page.jsx
import { useRouteError } from "react-router-dom";
export default function ErrorPage() {
const error = useRouteError();
console.error(error);
return (
Sorry, an unexpected error has occurred.
{error.statusText || error.message}
(Well, that's not much better. Maybe somebody forgot to ask the designer to make an error page. Maybe everybody forgets to ask the designer to make an error page and then blames the designer for not thinking of it π)
Note that [`useRouteError`][userouteerror] provides the error that was thrown. When the user navigates to routes that don't exist you'll get an [error response][isrouteerrorresponse] with a "Not Found" `statusText`. We'll see some other errors later in the tutorial and discuss them more.
For now, it's enough to know that pretty much all of your errors will now be handled by this page instead of infinite spinners, unresponsive pages, or blank screens π
## The Contact Route UI
Instead of a 404 "Not Found" page, we want to actually render something at the URLs we've linked to. For that, we need to make a new route.
π **Create the contact route module**
```sh
touch src/routes/contact.jsx
```
π **Add the contact component UI**
It's just a bunch of elements, feel free to copy/paste.
```jsx filename=src/routes/contact.jsx
import { Form } from "react-router-dom";
export default function Contact() {
const contact = {
first: "Your",
last: "Name",
avatar: "https://robohash.org/you.png?size=200x200",
twitter: "your_handle",
notes: "Some notes",
favorite: true,
};
return (
{contact.notes}
}
However, it's not inside of our root layout π
## Nested Routes
We want the contact component to render _inside_ of the `
We do it by making the contact route a _child_ of the root route.
π **Move the contacts route to be a child of the root route**
```jsx filename=src/main.jsx lines=[6-11]
const router = createBrowserRouter([
{
path: "/",
element:
## Data Writes + HTML Forms
We'll create our first contact in a second, but first let's talk about HTML.
React Router emulates HTML Form navigation as the data mutation primitive, according to web development before the JavaScript cambrian explosion. It gives you the UX capabilities of client rendered apps with the simplicity of the "old school" web model.
While unfamiliar to some web developers, HTML forms actually cause a navigation in the browser, just like clicking a link. The only difference is in the request: links can only change the URL while forms can also change the request method (GET vs POST) and the request body (POST form data).
Without client side routing, the browser will serialize the form's data automatically and send it to the server as the request body for POST, and as URLSearchParams for GET. React Router does the same thing, except instead of sending the request to the server, it uses client side routing and sends it to a route [`action`][action].
We can test this out by clicking the "New" button in our app. The app should blow up because the Vite server isn't configured to handle a POST request (it sends a 404, though it should probably be a 405 π€·).
Instead of sending that POST to the Vite server to create a new contact, let's use client side routing instead.
## Creating Contacts
We'll create new contacts by exporting an `action` in our root route, wiring it up to the route config, and changing our `
Reviewing the route config, the route looks like this:
```jsx
[
{
path: "contacts/:contactId",
element:
## Updating Data
Just like creating data, you update data with [`Name
## Updating Contacts with FormData
The edit route we just created already renders a form. All we need to do to update the record is wire up an action to the route. The form will post to the action and the data will be automatically revalidated.
π **Add an action to the edit module**
```jsx filename=src/routes/edit.jsx lines=[4,6,8-13]
import {
Form,
useLoaderData,
redirect,
} from "react-router-dom";
import { updateContact } from "../contacts";
export async function action({ request, params }) {
const formData = await request.formData();
const updates = Object.fromEntries(formData);
await updateContact(params.contactId, updates);
return redirect(`/contacts/${params.contactId}`);
}
/* existing code */
```
π **Wire the action up to the route**
```jsx filename=src/main.jsx lines=[3,23]
/* existing code */
import EditContact, {
action as editAction,
} from "./routes/edit";
const router = createBrowserRouter([
{
path: "/",
element:
## Mutation Discussion
> π It worked, but I have no idea what is going on here...
Let's dig in a bit...
Open up `src/routes/edit.jsx` and look at the form elements. Notice how they each have a name:
```jsx lines=[5] filename=src/routes/edit.jsx
```
Without JavaScript, when a form is submitted, the browser will create [`FormData`][formdata] and set it as the body of the request when it sends it to the server. As mentioned before, React Router prevents that and sends the request to your action instead, including the [`FormData`][formdata].
Each field in the form is accessible with `formData.get(name)`. For example, given the input field from above, you could access the first and last names like this:
```jsx lines=[3,4]
export async function action({ request, params }) {
const formData = await request.formData();
const firstName = formData.get("first");
const lastName = formData.get("last");
// ...
}
```
Since we have a handful of form fields, we used [`Object.fromEntries`][fromentries] to collect them all into an object, which is exactly what our `updateContact` function wants.
```jsx lines=[2,3]
const updates = Object.fromEntries(formData);
updates.first; // "Some"
updates.last; // "Name"
```
Aside from `action`, none of these APIs we're discussing are provided by React Router: [`request`][request], [`request.formData`][requestformdata], [`Object.fromEntries`][fromentries] are all provided by the web platform.
After we finished the action, note the [`redirect`][redirect] at the end:
```jsx filename=src/routes/edit.jsx lines=[5]
export async function action({ request, params }) {
const formData = await request.formData();
const updates = Object.fromEntries(formData);
await updateContact(params.contactId, updates);
return redirect(`/contacts/${params.contactId}`);
}
```
Loaders and actions can both [return a `Response`][returningresponses] (makes sense, since they received a [`Request`][request]!). The [`redirect`][redirect] helper just makes it easier to return a [response][response] that tells the app to change locations.
Without client side routing, if a server redirected after a POST request, the new page would fetch the latest data and render. As we learned before, React Router emulates this model and automatically revalidates the data on the page after the action. That's why the sidebar automatically updates when we save the form. The extra revalidation code doesn't exist without client side routing, so it doesn't need to exist with client side routing either!
## Redirecting new records to the edit page
Now that we know how to redirect, let's update the action that creates new contacts to redirect to the edit page:
π **Redirect to the new record's edit page**
```jsx filename=src/routes/root.jsx lines=[6,12]
import {
Outlet,
Link,
useLoaderData,
Form,
redirect,
} from "react-router-dom";
import { getContacts, createContact } from "../contacts";
export async function action() {
const contact = await createContact();
return redirect(`/contacts/${contact.id}/edit`);
}
```
Now when we click "New", we should end up on the edit page:
π **Add a handful of records**
I'm going to use the stellar lineup of speakers from the first Remix Conference π
## Active Link Styling
Now that we have a bunch of records, it's not clear which one we're looking at in the sidebar. We can use [`NavLink`][navlink] to fix this.
π **Use a `NavLink` in the sidebar**
```jsx filename=src/routes/root.jsx lines=[3,20-31]
import {
Outlet,
NavLink,
useLoaderData,
Form,
redirect,
} from "react-router-dom";
export default function Root() {
return (
<>
>
);
}
```
Note that we are passing a function to `className`. When the user is at the URL in the `NavLink`, then `isActive` will be true. When it's _about_ to be active (the data is still loading) then `isPending` will be true. This allows us to easily indicate where the user is, as well as provide immediate feedback on links that have been clicked but we're still waiting for data to load.
## Global Pending UI
As the user navigates the app, React Router will _leave the old page up_ as data is loading for the next page. You may have noticed the app feels a little unresponsive as you click between the list. Let's provide the user with some feedback so the app doesn't feel unresponsive.
React Router is managing all of the state behind the scenes and reveals the pieces of it you need to build dynamic web apps. In this case, we'll use the [`useNavigation`][usenavigation] hook.
π **`useNavigation` to add global pending UI**
```jsx filename=src/routes/root.jsx lines=[3,10,17-19]
import {
// existing code
useNavigation,
} from "react-router-dom";
// existing code
export default function Root() {
const { contacts } = useLoaderData();
const navigation = useNavigation();
return (
<>
Note that our data model (`src/contacts.js`) has a clientside cache, so navigating to the same contact is fast the second time. This behavior is _not_ React Router, it will re-load data for changing routes no matter if you've been there before or not. It does, however, avoid calling the loaders for _unchanging_ routes (like the list) during a navigation.
## Deleting Records
If we review code in the contact route, we can find the delete button looks like this:
```jsx filename=src/routes/contact.jsx lines=[3]
Recognize that screen? It's our [`errorElement`][errorelement] from before. The user, however, can't really do anything to recover from this screen except to hit refresh.
Let's create a contextual error message for the destroy route:
```jsx filename=src/main.jsx lines=[6]
[
/* other routes */
{
path: "contacts/:contactId/destroy",
action: destroyAction,
errorElement:
Our user now has more options than slamming refresh, they can continue to interact with the parts of the page that aren't having trouble π
Because the destroy route has its own `errorElement` and is a child of the root route, the error will render there instead of the root. As you probably noticed, these errors bubble up to the nearest `errorElement`. Add as many or as few as you like, as long as you've got one at the root.
## Index Routes
When we load up the app, you'll notice a big blank page on the right side of our list.
When a route has children, and you're at the parent route's path, the `
This is a demo for React Router.
Check out{" "}
the docs at reactrouter.com
.
Voila! No more blank space. It's common to put dashboards, stats, feeds, etc. at index routes. They can participate in data loading as well.
## Cancel Button
On the edit page we've got a cancel button that doesn't do anything yet. We'd like it to do the same thing as the browser's back button.
We'll need a click handler on the button as well as [`useNavigate`][usenavigate] from React Router.
π **Add the cancel button click handler with `useNavigate`**
```jsx filename=src/routes/edit.jsx lines=[5,10,20-22]
import {
Form,
useLoaderData,
redirect,
useNavigate,
} from "react-router-dom";
export default function EditContact() {
const { contact } = useLoaderData();
const navigate = useNavigate();
return (