Skip to content

Commit 371c30d

Browse files
committed
docs: overview
1 parent c2b4f96 commit 371c30d

File tree

1 file changed

+170
-0
lines changed

1 file changed

+170
-0
lines changed

docs/getting-started/overview.md

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -475,16 +475,175 @@ See:
475475

476476
## Optimistic UI
477477

478+
Knowing the [`formData`][formdata] being sent to an [action][action] is often enough to skip the busy indicators and render the UI in the next state immediately, even if your asynchronous work is still pending. This is called "optimistic UI".
479+
480+
```jsx
481+
function LikeButton({ tweet }) {
482+
const fetcher = useFetcher();
483+
484+
// if there is `formData` then it is posting to the action
485+
const liked = fetcher.formData
486+
? // check the formData to be optimistic
487+
fetcher.formData.get("liked") === "yes"
488+
: // if its not posting to the action, use the record's value
489+
tweet.liked;
490+
491+
return (
492+
<fetcher.Form method="post" action="toggle-liked">
493+
<button
494+
type="submit"
495+
name="liked"
496+
value={liked ? "yes" : "no"}
497+
/>
498+
</fetcher.Form>
499+
);
500+
}
501+
```
502+
503+
(Yes, HTML buttons can have a `name` and a `value`).
504+
505+
While it is more common to do optimistic UI with a [`fetcher`][fetcher], you can do the same with a normal form using [`navigation.formData`][navigationformdata].
506+
478507
## Data Fetchers
479508

509+
HTML Forms are the model for mutations but they have one major limitation: you can have only one at a time because a form submission is a navigation.
510+
511+
Most web apps need to allow for multiple mutations to be happening at the same time, like a list of records where each can be independently deleted, marked complete, liked, etc.
512+
513+
[Fetchers][fetcher] allow you to interact with the route [actions][action] and [loaders][loader] without causing a navigation in the browser, but still getting all the conventional benefits like error handling, revalidation, interruption handling, and race condition handling.
514+
515+
Imagine a list of tasks:
516+
517+
```jsx
518+
function Tasks() {
519+
const tasks = useLoaderData();
520+
return tasks.map((task) => (
521+
<div>
522+
<p>{task.name}</p>
523+
<ToggleCompleteButton task={task} />
524+
</div>
525+
));
526+
}
527+
```
528+
529+
Each task can be marked complete independently of the rest, with its own pending state and without causing a navigation with a [fetcher][fetcher]:
530+
531+
```jsx
532+
function ToggleCompleteButton({ task }) {
533+
const fetcher = useFetcher();
534+
535+
return (
536+
<fetcher.Form method="post" action="/toggle-complete">
537+
<fieldset disabled={fetcher.state !== "idle"}>
538+
<input type="hidden" name="id" value={task.id} />
539+
<input
540+
type="hidden"
541+
name="status"
542+
value={task.complete ? "incomplete" : "complete"}
543+
/>
544+
<button type="submit">
545+
{task.status === "complete"
546+
? "Mark Incomplete"
547+
: "Mark Complete"}
548+
</button>
549+
</fieldset>
550+
</fetcher.Form>
551+
);
552+
}
553+
```
554+
555+
See:
556+
557+
- [`useFetcher`][fetcher]
558+
480559
## Race Condition Handling
481560

561+
React Router will cancel stale operations and only commit fresh data automatically.
562+
563+
Any time you have asynchronous UI you have the risk of race conditions: when an async operation starts after but completes before an earlier operation. The result is a user interface that shows the wrong state.
564+
565+
Consider a search field that updates a list as the user types:
566+
567+
```
568+
?q=ry |---------------|
569+
^ commit wrong state
570+
?q=ryan |--------|
571+
^ lose correct state
572+
```
573+
574+
Even though the query for `q?=ryan` went out later, it completed earlier. If not handled correctly, the results will briefly be the correct values for `?q=ryan` but then flip over the incorrect results for `?q=ry`. Throttling and debouncing are not enough (you can still interrupt the requests that get through). You need to cancellation.
575+
576+
If you're using React Router's data conventions you avoid this problem completely and automatically.
577+
578+
```
579+
?q=ry |-----------X
580+
^ cancel wrong state when
581+
correct state completes earlier
582+
?q=ryan |--------|
583+
^ commit correct state
584+
```
585+
586+
Not only does React Router handle race conditions for a navigation like this, it also handles it for many other cases like loading results for an autocomplete or performing multiple concurrent mutations with [`fetcher`][fetcher] (and its automatic, concurrent revalidations).
587+
482588
## Error Handling
483589

590+
The vast majority of your application errors are handled automatically by React Router. It will catch any errors that are thrown while:
591+
592+
- rendering
593+
- loading data
594+
- updating data
595+
596+
In practice, this is pretty much every error in your app except those thrown in event handlers (`<button onClick>`) or `useEffect`. React Router apps tend to have very few of either.
597+
598+
When an error is thrown, instead of rendering the route's [`element`][element], the [`errorElement`][errorelement] is rendered.
599+
600+
```jsx
601+
<Route
602+
path="/"
603+
loader={() => {
604+
something.that.throws.an.error();
605+
}}
606+
// this will not be rendered
607+
element={<HappyPath />}
608+
// but this will instead
609+
errorElement={<ErrorBoundary />}
610+
/>
611+
```
612+
613+
If a route doesn't have an `errorElement`, the error will bubble to the nearest parent route with an `errorElement`:
614+
615+
```jsx
616+
<Route
617+
path="/"
618+
element={<HappyPath />}
619+
errorElement={<ErrorBoundary />}
620+
>
621+
{/* Errors here bubble up to the parent route */}
622+
<Route path="login" element={<Login />} />
623+
</Route>
624+
```
625+
626+
See:
627+
628+
- [`<Route errorElement>`][errorelement]
629+
- [`useRouteError`][userouteerror]
630+
484631
## Scroll Restoration
485632

633+
React Router will emulate the browser's scroll restoration on navigation, waiting for data to load before scrolling. This ensures the scroll position is restored to the right spot.
634+
635+
You can also customize the behavior by restoring based on something other than locations (like a url pathname) and preventing the scroll from happening on certain links (like tabs in the middle of a page).
636+
637+
See:
638+
639+
- [`<ScrollRestoration>`][scrollrestoration]
640+
486641
## Web Standard APIs
487642

643+
React Router is built on web standard APIs. [Loaders][loader] and [actions][action] receive standard Web Fetch API [`Request`][request] objects and can return [`Response`][response] objects, too. Cancellation is done with [Abort Signals][signal], search params are handled with [`URLSearchParams`][urlsearchparams], and data mutations are handled with [HTML Forms][htmlform].
644+
645+
When you get better at React Router, you get better at the web platform.
646+
488647
## Search Params
489648

490649
## Location State
@@ -508,3 +667,14 @@ See:
508667
[request]: https://developer.mozilla.org/en-US/docs/Web/API/Request
509668
[formdata]: https://developer.mozilla.org/en-US/docs/Web/API/FormData
510669
[creatingcontacts]: ../getting-started/tutorial#creating-contacts
670+
[navigationformdata]: ../hooks/use-navigation#navigationformdata
671+
[fetcher]: ../hooks/use-fetcher
672+
[errorelement]: ../route/error-element
673+
[userouteerror]: ../hooks/use-route-error
674+
[element]: ../route/route#element
675+
[scrollrestoration]: ../components/scroll-restoration
676+
[request]: https://developer.mozilla.org/en-US/docs/Web/API/Request
677+
[response]: https://developer.mozilla.org/en-US/docs/Web/API/Response
678+
[signal]: https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal
679+
[urlsearchparams]: https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams
680+
[htmlform]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form

0 commit comments

Comments
 (0)