From ff40b8d5bfca5c3ab6f26e898cdd4bf3c14f7805 Mon Sep 17 00:00:00 2001 From: gchang12 Date: Mon, 3 Nov 2025 15:28:28 -0800 Subject: [PATCH 01/31] Added backticks to filename; removed extra 'then'. --- docs/start/framework/data-loading.md | 2 +- docs/start/framework/route-module.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/start/framework/data-loading.md b/docs/start/framework/data-loading.md index b35995ef1b..d2f16f27b1 100644 --- a/docs/start/framework/data-loading.md +++ b/docs/start/framework/data-loading.md @@ -106,7 +106,7 @@ export default function Product({ } ``` -The URLs to pre-render are specified in react-router.config.ts: +The URLs to pre-render are specified in `react-router.config.ts`: ```ts filename=react-router.config.ts import type { Config } from "@react-router/dev/config"; diff --git a/docs/start/framework/route-module.md b/docs/start/framework/route-module.md index 68ab03d4c9..03be009527 100644 --- a/docs/start/framework/route-module.md +++ b/docs/start/framework/route-module.md @@ -480,7 +480,7 @@ The meta of the last matching route is used, allowing you to override parent rou ## `shouldRevalidate` -In framework mode with SSR, route loaders are automatically revalidated after all navigations and form submissions (this is different from [Data Mode][data-mode-should-revalidate]). This enables middleware and loaders to share a request context and optimize in different ways than then they would be in Data Mode. +In framework mode with SSR, route loaders are automatically revalidated after all navigations and form submissions (this is different from [Data Mode][data-mode-should-revalidate]). This enables middleware and loaders to share a request context and optimize in different ways than they would be in Data Mode. Defining this function allows you to opt out of revalidation for a route loader for navigations and form submissions. From 9d1bd837345be37de857b53a8a730ff5f4226287 Mon Sep 17 00:00:00 2001 From: gchang12 Date: Mon, 3 Nov 2025 15:36:00 -0800 Subject: [PATCH 02/31] Minor wording change for grammatical correctness. --- docs/start/framework/route-module.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/start/framework/route-module.md b/docs/start/framework/route-module.md index 03be009527..bba3cb5db6 100644 --- a/docs/start/framework/route-module.md +++ b/docs/start/framework/route-module.md @@ -480,7 +480,7 @@ The meta of the last matching route is used, allowing you to override parent rou ## `shouldRevalidate` -In framework mode with SSR, route loaders are automatically revalidated after all navigations and form submissions (this is different from [Data Mode][data-mode-should-revalidate]). This enables middleware and loaders to share a request context and optimize in different ways than they would be in Data Mode. +In framework mode with SSR, route loaders are automatically revalidated after all navigations and form submissions (this is different from [Data Mode][data-mode-should-revalidate]). This enables middleware and loaders to share a request context and optimize in different ways than they would in Data Mode. Defining this function allows you to opt out of revalidation for a route loader for navigations and form submissions. From b57fac17e64b2f3866d821aa08fb739c2879d15f Mon Sep 17 00:00:00 2001 From: George Chang <59218296+gchang12@users.noreply.github.com> Date: Mon, 3 Nov 2025 15:41:37 -0800 Subject: [PATCH 03/31] Update contributors.yml Signed name on line #132 --- contributors.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/contributors.yml b/contributors.yml index 08dd3651a3..6291fdd730 100644 --- a/contributors.yml +++ b/contributors.yml @@ -129,6 +129,7 @@ - gaspard - gatzjames - gavriguy +- gchang12 - Geist5000 - GeoffKarnov - gesposito From 88e2e2f7ebcba6cfed872ab6690560fee2b54b2b Mon Sep 17 00:00:00 2001 From: gchang12 Date: Sat, 15 Nov 2025 13:07:48 -0800 Subject: [PATCH 04/31] Changed 'they're' to 'it's; changed 'on on' to 'on'. --- docs/start/framework/route-module.md | 2 +- docs/start/framework/testing.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/start/framework/route-module.md b/docs/start/framework/route-module.md index bba3cb5db6..172b282060 100644 --- a/docs/start/framework/route-module.md +++ b/docs/start/framework/route-module.md @@ -137,7 +137,7 @@ See also: ## `clientMiddleware` -This is the client-side equivalent of `middleware` and runs in the browser during client navigations. The only difference from server middleware is that client middleware doesn't return Responses because they're not wrapping an HTTP request on the server. +This is the client-side equivalent of `middleware` and runs in the browser during client navigations. The only difference from server middleware is that client middleware doesn't return Responses because it's not wrapping an HTTP request on the server. Here's an example middleware to log requests on the client: diff --git a/docs/start/framework/testing.md b/docs/start/framework/testing.md index dd2059f020..536f97edf8 100644 --- a/docs/start/framework/testing.md +++ b/docs/start/framework/testing.md @@ -80,7 +80,7 @@ test("LoginForm renders error messages", async () => { ## Using with Framework Mode Types -It's important to note that `createRoutesStub` is designed for _unit_ testing of reusable components in your application that rely on on contextual router information (i.e., `loaderData`, `actionData`, `matches`). These components usually obtain this information via the hooks (`useLoaderData`, `useActionData`, `useMatches`) or via props passed down from the ancestor route component. We **strongly** recommend limiting your usage of `createRoutesStub` to unit testing of these types of reusable components. +It's important to note that `createRoutesStub` is designed for _unit_ testing of reusable components in your application that rely on contextual router information (i.e., `loaderData`, `actionData`, `matches`). These components usually obtain this information via the hooks (`useLoaderData`, `useActionData`, `useMatches`) or via props passed down from the ancestor route component. We **strongly** recommend limiting your usage of `createRoutesStub` to unit testing of these types of reusable components. `createRoutesStub` is _not designed_ for (and is arguably incompatible with) direct testing of Route components using the [`Route.\*`](../../explanation/type-safety) types available in Framework Mode. This is because the `Route.*` types are derived from your actual application - including the real `loader`/`action` functions as well as the structure of your route tree structure (which defines the `matches` type). When you use `createRoutesStub`, you are providing stubbed values for `loaderData`, `actionData`, and even your `matches` based on the route tree you pass to `createRoutesStub`. Therefore, the types won't align with the `Route.*` types and you'll get type issues trying to use a route component in a route stub. From 20685ee9df8f686988a9cc963e78e0ad84c853a3 Mon Sep 17 00:00:00 2001 From: gchang12 Date: Sat, 15 Nov 2025 13:08:12 -0800 Subject: [PATCH 05/31] Erased '// existing exports' line in codeblock of 'Loading Data'. The default 'App' function is part of those exports, so declaring App again will make `vite` raise an error. --- docs/tutorials/address-book.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/tutorials/address-book.md b/docs/tutorials/address-book.md index 3e356eb455..7651204635 100644 --- a/docs/tutorials/address-book.md +++ b/docs/tutorials/address-book.md @@ -408,12 +408,10 @@ First we'll create and export a [`clientLoader`][client-loader] function in the 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] +```tsx filename=app/root.tsx lines=[2,4-7,9-11,17-40] // existing imports import { getContacts } from "./data"; -// existing exports - export async function clientLoader() { const contacts = await getContacts(); return { contacts }; From 66aea2232f22fa983e7c9ad0e3567e4e9ed019fd Mon Sep 17 00:00:00 2001 From: gchang12 Date: Sat, 15 Nov 2025 13:30:23 -0800 Subject: [PATCH 06/31] Told user to transform the h1-text into a Link instead of adding a link to it. --- docs/tutorials/address-book.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorials/address-book.md b/docs/tutorials/address-book.md index 7651204635..ffc737cfc6 100644 --- a/docs/tutorials/address-book.md +++ b/docs/tutorials/address-book.md @@ -644,7 +644,7 @@ export default function About() { } ``` -👉 **Add a link to the about page in the sidebar** +👉 **Transform the text inside the h1-tags into a link to the about page in the sidebar** ```tsx filename=app/root.tsx lines=[5-7] export default function App() { From ed62a94300e0b0047ec2802280e279989141a984 Mon Sep 17 00:00:00 2001 From: gchang12 Date: Sat, 15 Nov 2025 13:37:55 -0800 Subject: [PATCH 07/31] Added an apostrophe. --- docs/tutorials/address-book.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorials/address-book.md b/docs/tutorials/address-book.md index ffc737cfc6..c6c71e720b 100644 --- a/docs/tutorials/address-book.md +++ b/docs/tutorials/address-book.md @@ -860,7 +860,7 @@ export async function loader() { } ``` -Whether you set `ssr` to `true` or `false` depends on you and your users needs. Both strategies are perfectly valid. For the remainder of this tutorial we're going to use server-side rendering, but know that all rendering strategies are first class citizens in React Router. +Whether you set `ssr` to `true` or `false` depends on you and your users' needs. Both strategies are perfectly valid. For the remainder of this tutorial we're going to use server-side rendering, but know that all rendering strategies are first class citizens in React Router. ## URL Params in Loaders From 754af467fdcff0d5f0c72a7aa365524569af37b2 Mon Sep 17 00:00:00 2001 From: gchang12 Date: Sat, 15 Nov 2025 14:13:03 -0800 Subject: [PATCH 08/31] Removed 'app/' prefix from instances of 'react-router.config.ts' --- docs/tutorials/address-book.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/tutorials/address-book.md b/docs/tutorials/address-book.md index c6c71e720b..a7176e70b9 100644 --- a/docs/tutorials/address-book.md +++ b/docs/tutorials/address-book.md @@ -811,7 +811,7 @@ If you refresh the about page, you still see the loading spinner for just a spli Inside of `react-router.config.ts`, we can add a [`prerender`][pre-rendering] array to the config to tell React Router to pre-render certain urls at build time. In this case we just want to pre-render the about page. -```ts filename=app/react-router.config.ts lines=[5] +```ts filename=react-router.config.ts lines=[5] import { type Config } from "@react-router/dev/config"; export default { @@ -836,7 +836,7 @@ If you ever do want to introduce server-side rendering into your React Router ap 👉 **Enable server-side rendering** -```ts filename=app/react-router.config.ts lines=[2] +```ts filename=react-router.config.ts lines=[2] export default { ssr: true, prerender: ["/about"], From 893b9723095c7ef088e9b98cea5e04473438e4b7 Mon Sep 17 00:00:00 2001 From: gchang12 Date: Sat, 15 Nov 2025 14:16:54 -0800 Subject: [PATCH 09/31] Told user explicitly to erase the existing 'contacts' const declaration. --- docs/tutorials/address-book.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/tutorials/address-book.md b/docs/tutorials/address-book.md index a7176e70b9..0824ff69c5 100644 --- a/docs/tutorials/address-book.md +++ b/docs/tutorials/address-book.md @@ -894,6 +894,10 @@ export default function Contact({ loaderData, }: Route.ComponentProps) { const { contact } = loaderData; + /* const contact = { + ... + }; + */ // existing code } From a25719cbb0c7c0f9a54f038a3644ad1fb67a6be3 Mon Sep 17 00:00:00 2001 From: gchang12 Date: Sat, 15 Nov 2025 14:27:23 -0800 Subject: [PATCH 10/31] Explicitly made it clear to override existing imports --- docs/tutorials/address-book.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/tutorials/address-book.md b/docs/tutorials/address-book.md index 0824ff69c5..030dac221e 100644 --- a/docs/tutorials/address-book.md +++ b/docs/tutorials/address-book.md @@ -933,7 +933,7 @@ Now, if the user isn't found, code execution down this path stops and React Rout 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, which used to be the only way prior to the JavaScript cambrian explosion. Don't be fooled by the simplicity! Forms in React Router give you the UX capabilities of client rendered apps with the simplicity of the "old school" web model. +React Router emulates HTML Form navigation as the data mutation primitive, which used to be the only way prior to the JavaScript cambrian explosion. Don't be fooled by the simplicity! Forms in React Router give you the UX capabilities of client-rendered apps with the simplicity of the "old school" web model. While unfamiliar to some web developers, HTML `form`s 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 `form`s can also change the request method (`GET` vs. `POST`) and the request body (`POST` form data). @@ -1096,11 +1096,12 @@ The edit route we just created already renders a `form`. All we need to do is ad 👉 **Add an `action` function to the edit route** -```tsx filename=app/routes/edit-contact.tsx lines=[1,4,8,6-15] +```tsx filename=app/routes/edit-contact.tsx lines=[1,4,7-15] import { Form, redirect } from "react-router"; +// import { Form } from "react-router"; // existing imports - import { getContact, updateContact } from "../data"; +// import { getContact } from "../data"; export async function action({ params, From ee47a90755d0119b025750124243ffc5cbb5db4d Mon Sep 17 00:00:00 2001 From: gchang12 Date: Sat, 15 Nov 2025 14:30:56 -0800 Subject: [PATCH 11/31] Changed order of import-lines --- docs/tutorials/address-book.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/tutorials/address-book.md b/docs/tutorials/address-book.md index 030dac221e..7b91b5630b 100644 --- a/docs/tutorials/address-book.md +++ b/docs/tutorials/address-book.md @@ -1096,12 +1096,12 @@ The edit route we just created already renders a `form`. All we need to do is ad 👉 **Add an `action` function to the edit route** -```tsx filename=app/routes/edit-contact.tsx lines=[1,4,7-15] +```tsx filename=app/routes/edit-contact.tsx lines=[1,5,7-15] import { Form, redirect } from "react-router"; // import { Form } from "react-router"; // existing imports -import { getContact, updateContact } from "../data"; // import { getContact } from "../data"; +import { getContact, updateContact } from "../data"; export async function action({ params, From 3a7c1cf39bfc37bf53b7c0aeb96c23c53eb5e55d Mon Sep 17 00:00:00 2001 From: gchang12 Date: Sat, 15 Nov 2025 14:35:03 -0800 Subject: [PATCH 12/31] TODO: Inspect this weird line containing the function, . --- docs/tutorials/address-book.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorials/address-book.md b/docs/tutorials/address-book.md index 7b91b5630b..69de6005de 100644 --- a/docs/tutorials/address-book.md +++ b/docs/tutorials/address-book.md @@ -1171,7 +1171,7 @@ export async function action({ params, request, }: Route.ActionArgs) { - invariant(params.contactId, "Missing contactId param"); + //invariant(params.contactId, "Missing contactId param"); // What is this? const formData = await request.formData(); const updates = Object.fromEntries(formData); await updateContact(params.contactId, updates); From 5e3c2c96156f3f4d5367b2f0b1c2b36663c2dc7f Mon Sep 17 00:00:00 2001 From: gchang12 Date: Sat, 15 Nov 2025 14:39:33 -0800 Subject: [PATCH 13/31] Re-added secretly erased imports: Form, Link --- docs/tutorials/address-book.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/tutorials/address-book.md b/docs/tutorials/address-book.md index 69de6005de..b83673d87d 100644 --- a/docs/tutorials/address-book.md +++ b/docs/tutorials/address-book.md @@ -1191,8 +1191,10 @@ Now that we know how to redirect, let's update the action that creates new conta 👉 **Redirect to the new record's edit page** -```tsx filename=app/root.tsx lines=[6,12] +```tsx filename=app/root.tsx lines=[8,14] import { + Form, // not really needed anymore + Link, // not really needed anymore Outlet, Scripts, ScrollRestoration, From 4bd3370c5906d96f528ee0cc79d7ac221b6c7a3f Mon Sep 17 00:00:00 2001 From: gchang12 Date: Sat, 15 Nov 2025 14:42:33 -0800 Subject: [PATCH 14/31] Explicitly erased old import-line --- docs/tutorials/address-book.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorials/address-book.md b/docs/tutorials/address-book.md index b83673d87d..dbf84d7391 100644 --- a/docs/tutorials/address-book.md +++ b/docs/tutorials/address-book.md @@ -1223,7 +1223,7 @@ Now that we have a bunch of records, it's not clear which one we're looking at i ```tsx filename=app/layouts/sidebar.tsx lines=[1,17-26,28] import { Form, Link, NavLink, Outlet } from "react-router"; - +//import { Form, Link, Outlet } from "react-router"; // existing imports and exports export default function SidebarLayout({ From 6caca478c83d62d4b746802a93c13f7d6336c186 Mon Sep 17 00:00:00 2001 From: gchang12 Date: Sat, 15 Nov 2025 14:45:39 -0800 Subject: [PATCH 15/31] More pedantic rewording. --- docs/tutorials/address-book.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorials/address-book.md b/docs/tutorials/address-book.md index dbf84d7391..c9c2b6459d 100644 --- a/docs/tutorials/address-book.md +++ b/docs/tutorials/address-book.md @@ -1306,7 +1306,7 @@ export default function SidebarLayout({ [`useNavigation`][use-navigation] returns the current navigation state: it can be one of `"idle"`, `"loading"` or `"submitting"`. -In our case, we add a `"loading"` class to the main part of the app if we're not idle. The CSS then adds a nice fade after a short delay (to avoid flickering the UI for fast loads). You could do anything you want though, like show a spinner or loading bar across the top. +In our case, we add a `"loading"` class to the main part of the app if it's not idle. The CSS then adds a nice fade after a short delay (to avoid flickering the UI for fast loads). You could do anything you want though, like show a spinner or loading bar across the top. From c1a53feb2819ebc4dcd8581bdf921e6e9371d253 Mon Sep 17 00:00:00 2001 From: gchang12 Date: Sat, 15 Nov 2025 14:52:41 -0800 Subject: [PATCH 16/31] Reduced ambiguity of where to place new route --- docs/tutorials/address-book.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/tutorials/address-book.md b/docs/tutorials/address-book.md index c9c2b6459d..cf92e67528 100644 --- a/docs/tutorials/address-book.md +++ b/docs/tutorials/address-book.md @@ -1353,7 +1353,6 @@ export default [ "contacts/:contactId/destroy", "routes/destroy-contact.tsx", ), - // existing routes ] satisfies RouteConfig; ``` From ea5f76c91d2db8314cb9b3ec1c1b618b13160cce Mon Sep 17 00:00:00 2001 From: gchang12 Date: Sat, 15 Nov 2025 14:53:48 -0800 Subject: [PATCH 17/31] More pedantic rewording. --- docs/tutorials/address-book.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorials/address-book.md b/docs/tutorials/address-book.md index cf92e67528..390801a9c2 100644 --- a/docs/tutorials/address-book.md +++ b/docs/tutorials/address-book.md @@ -1380,7 +1380,7 @@ When the user clicks the submit button: 2. The `
` matches the new route at `contacts/:contactId/destroy` and sends it the request 3. After the `action` redirects, React Router calls all the `loader`s for the data on the page to get the latest values (this is "revalidation"). `loaderData` in `routes/contact.tsx` now has new values and causes the components to update! -Add a `Form`, add an `action`, React Router does the rest. +Add a `Form`, add an `action`, and React Router does the rest. ## Cancel Button From e4c26e94d1abcc7fe7ff57fbbba28f8ad9377a71 Mon Sep 17 00:00:00 2001 From: gchang12 Date: Sat, 15 Nov 2025 14:57:48 -0800 Subject: [PATCH 18/31] Explicitly commented out previous import that would conflict with the new one. --- docs/tutorials/address-book.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/docs/tutorials/address-book.md b/docs/tutorials/address-book.md index 390801a9c2..fbb691c3ba 100644 --- a/docs/tutorials/address-book.md +++ b/docs/tutorials/address-book.md @@ -1390,9 +1390,10 @@ We'll need a click handler on the button as well as [`useNavigate`][use-navigate 👉 **Add the cancel button click handler with `useNavigate`** -```tsx filename=app/routes/edit-contact.tsx lines=[1,8,15] +```tsx filename=app/routes/edit-contact.tsx lines=[1,9,16] import { Form, redirect, useNavigate } from "react-router"; -// existing imports & exports +//import { Form, redirect } from "react-router"; +// existing imports, and existing exports except for `EditContact` export default function EditContact({ loaderData, @@ -1405,9 +1406,7 @@ export default function EditContact({ {/* existing elements */}

- +

); From 3cce7df4592b3af2937c7c1fba330ed2d464fac5 Mon Sep 17 00:00:00 2001 From: gchang12 Date: Sat, 15 Nov 2025 15:02:19 -0800 Subject: [PATCH 19/31] Explicitly commented out previous definition of --- docs/tutorials/address-book.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/tutorials/address-book.md b/docs/tutorials/address-book.md index fbb691c3ba..75426164de 100644 --- a/docs/tutorials/address-book.md +++ b/docs/tutorials/address-book.md @@ -1442,7 +1442,7 @@ Since it's not `
`, React Router emulates the browser by seri 👉 **Filter the list if there are `URLSearchParams`** ```tsx filename=app/layouts/sidebar.tsx lines=[3-8] -// existing imports & exports +// existing imports, and the existing exports except for `loader` export async function loader({ request, @@ -1450,6 +1450,8 @@ export async function loader({ const url = new URL(request.url); const q = url.searchParams.get("q"); const contacts = await getContacts(q); +//export async function loader() { + //const contacts = await getContacts(); return { contacts }; } From 4e375871273c8ffb13e5cbea636a817c62a892cb Mon Sep 17 00:00:00 2001 From: gchang12 Date: Sat, 15 Nov 2025 15:03:43 -0800 Subject: [PATCH 20/31] Added more verbosity --- docs/tutorials/address-book.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorials/address-book.md b/docs/tutorials/address-book.md index 75426164de..76cdb3ed86 100644 --- a/docs/tutorials/address-book.md +++ b/docs/tutorials/address-book.md @@ -1460,7 +1460,7 @@ export async function loader({ -Because this is a `GET`, not a `POST`, React Router _does not_ call the `action` function. Submitting a `GET` `form` is the same as clicking a link: only the URL changes. +Because this is a `GET` request and not a `POST` request, React Router _does not_ call the `action` function. Submitting a `GET` `form` is the same as clicking a link: only the URL changes. This also means it's a normal page navigation. You can click the back button to get back to where you were. From a49b5f437783d2ceb82b822a7d1995620f96a9c6 Mon Sep 17 00:00:00 2001 From: gchang12 Date: Sat, 15 Nov 2025 15:06:54 -0800 Subject: [PATCH 21/31] More rewordings --- docs/tutorials/address-book.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/tutorials/address-book.md b/docs/tutorials/address-book.md index 76cdb3ed86..41efa70671 100644 --- a/docs/tutorials/address-book.md +++ b/docs/tutorials/address-book.md @@ -1468,14 +1468,14 @@ This also means it's a normal page navigation. You can click the back button to There are a couple of UX issues here that we can take care of quickly. -1. If you click back after a search, the form field still has the value you entered even though the list is no longer filtered. -2. If you refresh the page after searching, the form field no longer has the value in it, even though the list is filtered +1. If you click back after a search, the form field will still have the value you entered even though the list is no longer filtered. +2. If you refresh the page after searching, the form field will no longer have the value in it, even though the list is filtered. -In other words, the URL and our input's state are out of sync. +In other words, the URL's and our input's states are out of sync. -Let's solve (2) first and start the input with the value from the URL. +Let's solve (2) first and initialize the input with the value from the URL. -👉 **Return `q` from your `loader`, set it as the input's default value** +👉 **Return `q` from your `loader` and set it as the input's default value** ```tsx filename=app/layouts/sidebar.tsx lines=[9,15,26] // existing imports & exports From e505b1e0d80afa5d58aa13a415a8b1960a2f2e62 Mon Sep 17 00:00:00 2001 From: gchang12 Date: Sat, 15 Nov 2025 15:09:49 -0800 Subject: [PATCH 22/31] Updated another code block --- docs/tutorials/address-book.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/docs/tutorials/address-book.md b/docs/tutorials/address-book.md index 41efa70671..7f2feb70f4 100644 --- a/docs/tutorials/address-book.md +++ b/docs/tutorials/address-book.md @@ -1523,16 +1523,14 @@ export default function SidebarLayout({ The input field will show the query if you refresh the page after a search now. -Now for problem (1), clicking the back button and updating the input. We can bring in `useEffect` from React to manipulate the input's value in the DOM directly. +Now for problem (1): clicking the back button and updating the input. We can bring in `useEffect` from React to manipulate the input's value in the DOM directly. 👉 **Synchronize input value with the `URLSearchParams`** -```tsx filename=app/layouts/sidebar.tsx lines=[2,12-17] +```tsx filename=app/layouts/sidebar.tsx lines=[2,10-15] // existing imports import { useEffect } from "react"; -// existing imports & exports - export default function SidebarLayout({ loaderData, }: Route.ComponentProps) { From 8dc194592f222fccd5d4ade43f5d572334a897ea Mon Sep 17 00:00:00 2001 From: gchang12 Date: Sat, 15 Nov 2025 15:11:15 -0800 Subject: [PATCH 23/31] Reworded a sentence for the chance to use 'implement' in a sentence. --- docs/tutorials/address-book.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorials/address-book.md b/docs/tutorials/address-book.md index 7f2feb70f4..8c6cecb8c7 100644 --- a/docs/tutorials/address-book.md +++ b/docs/tutorials/address-book.md @@ -1550,7 +1550,7 @@ export default function SidebarLayout({ > 🤔 Shouldn't you use a controlled component and React State for this? -You could certainly do this as a controlled component. You will have more synchronization points, but it's up to you. +You certainly could implement this using a controlled component. You will have more synchronization points, but it's up to you.
From d96b7bf92db08394c03288f14c49f6e1365e27ac Mon Sep 17 00:00:00 2001 From: gchang12 Date: Sat, 15 Nov 2025 15:16:13 -0800 Subject: [PATCH 24/31] In my opinion, the extra whitespace should be highlighted too. Or at least, you should put the existing attributes on the same line as the tag as follows: submit(event.currentTarget) } --- docs/tutorials/address-book.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/tutorials/address-book.md b/docs/tutorials/address-book.md index 8c6cecb8c7..f0b0bd3c16 100644 --- a/docs/tutorials/address-book.md +++ b/docs/tutorials/address-book.md @@ -1617,7 +1617,7 @@ We've got a product decision to make here. Sometimes you want the user to submit We've seen `useNavigate` already, we'll use its cousin, [`useSubmit`][use-submit], for this. -```tsx filename=app/layouts/sidebar.tsx lines=[7,16,27-29] +```tsx filename=app/layouts/sidebar.tsx lines=[7,16,25-29] import { Form, Link, @@ -1626,7 +1626,7 @@ import { useNavigation, useSubmit, } from "react-router"; -// existing imports & exports +// existing imports, and existing exports except for `SidebarLayout` export default function SidebarLayout({ loaderData, From 938d88e14419a67f3b5b7a23484c887d70dce1cd Mon Sep 17 00:00:00 2001 From: gchang12 Date: Sat, 15 Nov 2025 15:25:21 -0800 Subject: [PATCH 25/31] Reworded some things --- docs/tutorials/address-book.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/tutorials/address-book.md b/docs/tutorials/address-book.md index f0b0bd3c16..66999e88b1 100644 --- a/docs/tutorials/address-book.md +++ b/docs/tutorials/address-book.md @@ -1663,17 +1663,17 @@ export default function SidebarLayout({ As you type, the `form` is automatically submitted now! -Note the argument to [`submit`][use-submit]. The `submit` function will serialize and submit any form you pass to it. We're passing in `event.currentTarget`. The `currentTarget` is the DOM node the event is attached to (the `form`). +Note the argument to [`submit`][use-submit]. The `submit` function will serialize and submit any form you pass to it. We're passing in `event.currentTarget`. The `currentTarget` is the DOM node the event is attached to (i.e., the `form`). -## Adding Search Spinner +## Adding a Search Spinner -In a production app, it's likely this search will be looking for records in a database that is too large to send all at once and filter client side. That's why this demo has some faked network latency. +In a production app, a given search of a database will likely return a set of records that is too large to send all at once to a client and be filtered client-side. This is why this demo has some faked network latency. -Without any loading indicator, the search feels kinda sluggish. Even if we could make our database faster, we'll always have the user's network latency in the way and out of our control. +Without any loading indicator, the search will seem a bit sluggish. Even if we could make our database faster, the the user's network latency will always be in the way and out of our control. For a better user experience, let's add some immediate UI feedback for the search. We'll use [`useNavigation`][use-navigation] again. -👉 **Add a variable to know if we're searching** +👉 **Add a variable to track the status of a search operation** ```tsx filename=app/layouts/sidebar.tsx lines=[9-13] // existing imports & exports From ca52c1d03f743d11a5e96e3356db3b368a61ecfc Mon Sep 17 00:00:00 2001 From: gchang12 Date: Sat, 15 Nov 2025 15:36:39 -0800 Subject: [PATCH 26/31] Explicitly commented out old Forms that needed replacement; added some minor rewording. --- docs/tutorials/address-book.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/tutorials/address-book.md b/docs/tutorials/address-book.md index 66999e88b1..d37c834d6d 100644 --- a/docs/tutorials/address-book.md +++ b/docs/tutorials/address-book.md @@ -1821,7 +1821,7 @@ export default function SidebarLayout({ } ``` -After a quick check if this is the first search or not, we decide to replace. Now the first search will add a new entry, but every keystroke after that will replace the current entry. Instead of clicking back 7 times to remove the search, users only have to click back once. +After a quick check to see if this is the first search or not, we decide to replace. Now the first search will add a new entry, but every keystroke after that will replace the current entry. Instead of clicking back 7 times to remove the search, users only have to click back once. ## `Form`s Without Navigation @@ -1833,9 +1833,9 @@ The ★ button on the contact page makes sense for this. We aren't creating or d 👉 **Change the `` form to a fetcher form** -```tsx filename=app/routes/contact.tsx lines=[1,10,14,26] +```tsx filename=app/routes/contact.tsx lines=[1,10,15,27] import { Form, useFetcher } from "react-router"; - +// import { Form } from "react-router"; // existing imports & exports function Favorite({ @@ -1847,6 +1847,7 @@ function Favorite({ const favorite = contact.favorite; return ( + {/* */}