Skip to content

Commit 9908786

Browse files
committed
docs: tutorial stuff
1 parent d2e7c5b commit 9908786

File tree

2 files changed

+114
-3
lines changed

2 files changed

+114
-3
lines changed

docs/getting-started/concepts.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ order: 5
55

66
# Main Concepts
77

8+
<docs-warning>This document needs to be updated for 6.4 data APIs</docs-warning>
9+
810
<docs-warning>This document is a deep dive into the core concepts behind routing as implemented in React Router. It's pretty long, so if you're looking for a more practical guide check out our [quick start tutorial][tutorial].</docs-warning>
911

1012
You might be wondering what exactly React Router does. How can it help you build your app? What exactly is a **router**, anyway?

docs/getting-started/tutorial.md

Lines changed: 112 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1812,12 +1812,121 @@ function Favorite({ contact }) {
18121812

18131813
If you click the button now you should see the star _immediately_ change to the new state. Instead of always rendering the actual data, we check if the fetcher has any `formData` being submitted, if so, we'll use that instead. When the action is done, the `fetcher.formData` will no longer exist and we're back to using the actual data. So even if you write bugs in your optimistic UI code, it'll eventually go back to the correct state 🥹
18141814

1815-
In fact, you can click that button as fast as you want with the choppiest network, React Router will automatically handle all interruptions and race conditions on the revalidation. If you're making a `fetch` call in your action, you can even watch it happen on the network tab, it's pretty cool.
1815+
## Not Found Data
1816+
1817+
What happens if the contact we're trying load doesn't exist?
1818+
1819+
<img loading="lazy" class="tutorial" src="/_docs/tutorial/25.webp" />
1820+
1821+
Our root [`errorElement`][errorelement] is catching this unexpected error as we try to render a `null` contact. Nice the error was properly handled, but we can do better!
1822+
1823+
Whenever you have an expected error case in a loader or action–like the data not existing–you can `throw`. The call stack will break, React Router will catch it, and the error path is rendered instead. We won't even try to render a `null` contact.
1824+
1825+
👉 **Throw a 404 response in the loader**
1826+
1827+
```jsx filename=src/routes/contact.jsx lines lines=[4]
1828+
export async function loader({ params }) {
1829+
const contact = await getContact(params.contactId);
1830+
if (!contact) {
1831+
throw new Response("", {
1832+
status: 404,
1833+
statusText: "Not Found",
1834+
});
1835+
}
1836+
return contact;
1837+
}
1838+
```
1839+
1840+
<img loading="lazy" class="tutorial" src="/_docs/tutorial/27.webp" />
1841+
1842+
Instead of hitting a render error with `Cannot read properties of null`, we avoid the component completely and render the error path instead, telling the user something more specific.
1843+
1844+
This keeps your happy paths, happy. Your route elements don't need to concern themselves with error and loading states.
1845+
1846+
## Pathless Routes
1847+
1848+
One last thing. The last error page we saw would be better if it rendered inside the root outlet, instead of the whole page. In fact, every error in all of our child routes would be better in the outlet, then the user has more options than hitting refresh.
1849+
1850+
We'd like it to look like this:
1851+
1852+
<img loading="lazy" class="tutorial" src="/_docs/tutorial/26.webp" />
1853+
1854+
We could add the error element to every one of the child routes but, since it's all the same error page, this isn't recommended:
1855+
1856+
```jsx filename=src/main.jsx lines=[11,18,25,30]
1857+
<Route
1858+
path="/"
1859+
element={<Root />}
1860+
loader={rootLoader}
1861+
action={rootAction}
1862+
errorElement={<ErrorPage />}
1863+
>
1864+
<Route
1865+
index
1866+
element={<Index />}
1867+
errorElement={<ErrorPage />}
1868+
/>
1869+
<Route
1870+
path="contacts/:contactId"
1871+
element={<Contact />}
1872+
loader={contactLoader}
1873+
action={contactAction}
1874+
errorElement={<ErrorPage />}
1875+
/>
1876+
<Route
1877+
path="contacts/:contactId/edit"
1878+
element={<EditContact />}
1879+
loader={contactLoader}
1880+
action={editAction}
1881+
errorElement={<ErrorPage />}
1882+
/>
1883+
<Route
1884+
path="contacts/:contactId/destroy"
1885+
action={destroyAction}
1886+
errorElement={<ErrorPage />}
1887+
/>
1888+
</Route>
1889+
```
1890+
1891+
There's a cleaner way. Routes can be used _without_ a path, which lets them participate in the UI layout without requiring new path segments in the URL. Check it out:
1892+
1893+
👉 **Wrap the child routes in a pathless route**
1894+
1895+
```jsx filename=src/main.jsx lines=[8,26]
1896+
<Route
1897+
path="/"
1898+
element={<Root />}
1899+
loader={rootLoader}
1900+
action={rootAction}
1901+
errorElement={<ErrorPage />}
1902+
>
1903+
<Route errorElement={<ErrorPage />}>
1904+
<Route index element={<Index />} />
1905+
<Route
1906+
path="contacts/:contactId"
1907+
element={<Contact />}
1908+
loader={contactLoader}
1909+
action={contactAction}
1910+
/>
1911+
<Route
1912+
path="contacts/:contactId/edit"
1913+
element={<EditContact />}
1914+
loader={contactLoader}
1915+
action={editAction}
1916+
/>
1917+
<Route
1918+
path="contacts/:contactId/destroy"
1919+
action={destroyAction}
1920+
/>
1921+
</Route>
1922+
</Route>
1923+
```
1924+
1925+
When any errors are thrown in the child routes, our new pathless route will catch it and render, preserving the root route's UI!
18161926

18171927
---
18181928

1819-
- 404
1820-
- replace delete
1929+
That's it! Thanks for giving React Router a shot. We hope this tutorial gives you a solid start to build great user experiences. There's a lot more you can do with React Router, so make sure to check out all the APIs 😀
18211930

18221931
[vite]: https://vitejs.dev/guide/
18231932
[node]: https://nodejs.org

0 commit comments

Comments
 (0)