Skip to content

Commit d8e6d7f

Browse files
authored
docs: add better docs and console errors for data router features (#9311)
* docs: add data router note to ScrollRestoration * Add trailing period * Add changeset * doh\!
1 parent 41c0491 commit d8e6d7f

File tree

16 files changed

+113
-43
lines changed

16 files changed

+113
-43
lines changed

.changeset/nasty-bugs-promise.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"react-router": patch
3+
"react-router-dom": patch
4+
---
5+
6+
docs: Enhance console error messages for invalid usage of data router hooks

docs/components/form.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ new: true
77

88
The Form component is a wrapper around a plain HTML [form][htmlform] that emulates the browser for client side routing and data mutations. It is _not_ a form validation/state management library like you might be used to in the React ecosystem (for that, we recommend the browser's built in [HTML Form Validation][formvalidation] and data validation on your backend server).
99

10+
<docs-warning>This feature only works if using a data router, see [Picking a Router][pickingarouter]</docs-warning>
11+
1012
```tsx
1113
import { Form } from "react-router-dom";
1214

@@ -311,3 +313,4 @@ You can access those values from the `request.url`
311313
[remix]: https://remix.run
312314
[formvalidation]: https://developer.mozilla.org/en-US/docs/Learn/Forms/Form_validation
313315
[indexsearchparam]: ../guides/index-search-param
316+
[pickingarouter]: ../routers/picking-a-router

docs/components/scroll-restoration.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ new: true
77

88
This component will emulate the browser's scroll restoration on location changes after loaders have completed to ensure the scroll position is restored to the right spot, even across domains.
99

10+
<docs-warning>This feature only works if using a data router, see [Picking a Router][pickingarouter]</docs-warning>
11+
1012
You should only render one of these and it's recommended you render it in the root route of your app:
1113

1214
```tsx [1,7]
@@ -97,3 +99,4 @@ Server Rendering frameworks can prevent scroll flashing because they can send a
9799

98100
[remix]: https://remix.run
99101
[preventscrollreset]: ../components/link#preventscrollreset
102+
[pickingarouter]: ../routers/picking-a-router

docs/hooks/use-fetcher.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,12 @@ new: true
77

88
In HTML/HTTP, data mutations and loads are modeled with navigation: `<a href>` and `<form action>`. Both cause a navigation in the browser. The React Router equivalents are [`<Link>`][link] and [`<Form>`][form].
99

10-
But sometimes you want to call a loader outside of navigation, or call an action (and get the data on the page to revalidate) without changing the URL. Or you need to have multiple mutations in-flight at the same time.
10+
But sometimes you want to call a [`loader`][loader] outside of navigation, or call an [`action`][action] (and get the data on the page to revalidate) without changing the URL. Or you need to have multiple mutations in-flight at the same time.
1111

1212
Many interactions with the server aren't navigation events. This hook lets you plug your UI into your actions and loaders without navigating.
1313

14+
<docs-warning>This feature only works if using a data router, see [Picking a Router][pickingarouter]</docs-warning>
15+
1416
This is useful when you need to:
1517

1618
- fetch data not associated with UI routes (popovers, dynamic forms, etc.)
@@ -220,6 +222,9 @@ Tells you the method of the form being submitted: get, post, put, patch, or dele
220222
fetcher.formMethod; // "post"
221223
```
222224

225+
[loader]: ../route/loader
226+
[action]: ../route/action
227+
[pickingarouter]: ../routers/picking-a-router
223228
[indexsearchparam]: ../guides/index-search-param
224229
[link]: ../components/link
225230
[form]: ../components/form

docs/hooks/use-fetchers.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ new: true
77

88
Returns an array of all inflight [fetchers][usefetcher] without their `load`, `submit`, or `Form` properties (can't have parent components trying to control the behavior of their children! We know from IRL experience that this is a fool's errand.)
99

10+
<docs-warning>This feature only works if using a data router, see [Picking a Router][pickingarouter]</docs-warning>
11+
1012
```tsx
1113
import { useFetchers } from "react-router-dom";
1214

@@ -134,3 +136,4 @@ function ProjectTaskCount({ project }) {
134136
It's a little bit of work, but it's mostly just asking React Router for the state it's tracking and doing an optimistic calculation based on it.
135137

136138
[usefetcher]: ./use-fetcher
139+
[pickingarouter]: ../routers/picking-a-router

docs/hooks/use-matches.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ A `match` has the following shape:
3939

4040
Pairing `<Route handle>` with `useMatches` gets very powerful since you can put whatever you want on a route `handle` and have access to `useMatches` anywhere.
4141

42-
<docs-warning>`useMatches` only works with Data Routers, since they know the full route tree up front and can provide all of the current matches. Additionally, `useMatches` will not match down into any descendant route trees since the router isn't aware of the descendant routes.</docs-warning>
42+
<docs-warning>This feature only works if using a data router (see [Picking a Router][pickingarouter]) since they know the full route tree up front and can provide all of the current matches. Additionally, `useMatches` will not match down into any descendant route trees since the router isn't aware of the descendant routes.</docs-warning>
4343

4444
## Breadcrumbs
4545

@@ -98,3 +98,5 @@ function Breadcrumbs() {
9898
```
9999
100100
Now you can render `<Breadcrumbs/>` anywhere you want, probably in the root component.
101+
102+
[pickingarouter]: ../routers/picking-a-router

docs/hooks/use-navigation.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ This hook tells you everything you need to know about a page navigation to build
1313
- Optimistically showing a new record while it's being created on the server
1414
- Optimistically showing the new state of a record while it's being updated
1515

16+
<docs-warning>This feature only works if using a data router, see [Picking a Router][pickingarouter]</docs-warning>
17+
1618
```js
1719
import { useNavigation } from "react-router-dom";
1820

@@ -95,3 +97,4 @@ This tells you what the next [location][location] is going to be.
9597
Note that this link will not appear "pending" if a form is being submitted to the URL the link points to, because we only do this for "loading" states. The form will contain the pending UI for when the state is "submitting", once the action is complete, then the link will go pending.
9698

9799
[location]: ../utils/location
100+
[pickingarouter]: ../routers/picking-a-router

docs/hooks/use-revalidator.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ new: true
77

88
This hook allows you to revalidate the data for any reason. React Router automatically revalidates the data after actions are called, but you may want to revalidate for other reasons like when focus returns to the window.
99

10+
<docs-warning>This feature only works if using a data router, see [Picking a Router][pickingarouter]</docs-warning>
11+
1012
```tsx
1113
import { useRevalidator } from "react-router-dom";
1214

@@ -61,3 +63,4 @@ If a navigation happens while a revalidation is in flight, the revalidation will
6163
[form]: ../components/form
6264
[usefetcher]: ./use-fetcher
6365
[usesubmit]: ./use-submit
66+
[pickingarouter]: ../routers/picking-a-router

docs/hooks/use-route-error.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ new: true
77

88
Inside of an [`errorElement`][errorelement], this hooks returns anything thrown during an action, loader, or rendering. Note that thrown responses have special treatment, see [`isRouteErrorResponse`][isrouteerrorresponse] for more information.
99

10+
<docs-warning>This feature only works if using a data router, see [Picking a Router][pickingarouter]</docs-warning>
11+
1012
```jsx
1113
function ErrorBoundary() {
1214
const error = useRouteError();
@@ -33,3 +35,4 @@ function ErrorBoundary() {
3335

3436
[errorelement]: ../route/error-element
3537
[isrouteerrorresponse]: ../utils/is-route-error-response
38+
[pickingarouter]: ../routers/picking-a-router

docs/hooks/use-route-loader-data.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ new: true
77

88
This hook makes the data at any currently rendered route available anywhere in the tree. This is useful for components deep in the tree needing data from routes much farther up, as well as parent routes needing the data of child routes deeper in the tree.
99

10+
<docs-warning>This feature only works if using a data router, see [Picking a Router][pickingarouter]</docs-warning>
11+
1012
```tsx
1113
import { useRouteLoaderData } from "react-router-dom";
1214

docs/hooks/use-submit.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,11 @@ new: true
55

66
# `useSubmit`
77

8-
The imperative version of `<Form>` that let's you, the programmer, submit a form instead of the user. For example, submitting the form every time a value changes inside the form:
8+
The imperative version of `<Form>` that let's you, the programmer, submit a form instead of the user.
9+
10+
<docs-warning>This feature only works if using a data router, see [Picking a Router][pickingarouter]</docs-warning>
11+
12+
For example, submitting the form every time a value changes inside the form:
913

1014
```tsx [8]
1115
import { useSubmit, Form } from "react-router-dom";
@@ -87,3 +91,5 @@ submit(null, {
8791
// same as
8892
<Form action="/logout" method="post" />;
8993
```
94+
95+
[pickingarouter]: ../routers/picking-a-router

docs/route/action.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ new: true
77

88
Route actions are the "writes" to route [loader][loader] "reads". They provide a way for apps to perform data mutations with simple HTML and HTTP semantics while React Router abstracts away the complexity of asynchronous UI and revalidation. This gives you the simple mental model of HTML + HTTP (where the browser handles the asynchrony and revalidation) with the behavior and and UX capabilities of modern SPAs.
99

10-
<docs-error>This feature only works if using a data router</docs-error>
10+
<docs-warning>This feature only works if using a data router, see [Picking a Router][pickingarouter]</docs-warning>
1111

1212
```tsx
1313
<Route
@@ -130,6 +130,7 @@ You can `throw` in your action to break out of the current call stack (stop runn
130130
For more details and expanded use cases, read the [errorElement][errorelement] documentation.
131131

132132
[loader]: ./loader
133+
[pickingarouter]: ../routers/picking-a-router
133134
[dynamicsegments]: ./route#dynamic-segments
134135
[formdata]: https://developer.mozilla.org/en-US/docs/Web/API/FormData
135136
[request]: https://developer.mozilla.org/en-US/docs/Web/API/Request

docs/route/error-element.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ new: true
77

88
When exceptions are thrown in [loaders][loader], [actions][action], or component rendering, instead of the normal render path for your Routes (`<Route element>`), the error path will be rendered (`<Route errorElement>`) and the error made available with [`useRouteError`][userouteerror].
99

10-
<docs-error>This feature only works if using a data router</docs-error>
10+
<docs-warning>This feature only works if using a data router, see [Picking a Router][pickingarouter]</docs-warning>
1111

1212
```tsx
1313
<Route
@@ -210,6 +210,7 @@ The project route doesn't have to think about errors at all. Between the loader
210210
[loader]: ./loader
211211
[action]: ./action
212212
[userouteerror]: ../hooks/use-route-error
213+
[pickingarouter]: ../routers/picking-a-router
213214
[response]: https://developer.mozilla.org/en-US/docs/Web/API/Response
214215
[isrouteerrorresponse]: ../utils/is-route-error-response
215216
[json]: ../fetch/json

docs/route/should-revalidate.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ new: true
77

88
This function allows you opt-out of revalidation for a route's loader as an optimization.
99

10+
<docs-warning>This feature only works if using a data router, see [Picking a Router][pickingarouter]</docs-warning>
11+
1012
There are several instances where data is revalidated, keeping your UI in sync with your data automatically:
1113

1214
- After an [`action`][action] is called from a [`<Form>`][form].
@@ -76,3 +78,4 @@ interface ShouldRevalidateFunction {
7678
[loader]: ./loader
7779
[useloaderdata]: ../hooks/use-loader-data
7880
[params]: ./route#dynamic-segments
81+
[pickingarouter]: ../routers/picking-a-router

packages/react-router-dom/index.tsx

Lines changed: 36 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -642,6 +642,35 @@ if (__DEV__) {
642642
//#region Hooks
643643
////////////////////////////////////////////////////////////////////////////////
644644

645+
enum DataRouterHook {
646+
UseScrollRestoration = "useScrollRestoration",
647+
UseSubmitImpl = "useSubmitImpl",
648+
UseFetcher = "useFetcher",
649+
}
650+
651+
enum DataRouterStateHook {
652+
UseFetchers = "useFetchers",
653+
UseScrollRestoration = "useScrollRestoration",
654+
}
655+
656+
function getDataRouterConsoleError(
657+
hookName: DataRouterHook | DataRouterStateHook
658+
) {
659+
return `${hookName} must be used within a data router. See https://reactrouter.com/en/main/routers/picking-a-router.`;
660+
}
661+
662+
function useDataRouterContext(hookName: DataRouterHook) {
663+
let ctx = React.useContext(DataRouterContext);
664+
invariant(ctx, getDataRouterConsoleError(hookName));
665+
return ctx;
666+
}
667+
668+
function useDataRouterState(hookName: DataRouterStateHook) {
669+
let state = React.useContext(DataRouterStateContext);
670+
invariant(state, getDataRouterConsoleError(hookName));
671+
return state;
672+
}
673+
645674
/**
646675
* Handles the click behavior for router `<Link>` components. This is useful if
647676
* you need to create custom `<Link>` components with the same click behavior we
@@ -789,12 +818,7 @@ export function useSubmit(): SubmitFunction {
789818
}
790819

791820
function useSubmitImpl(fetcherKey?: string, routeId?: string): SubmitFunction {
792-
let dataRouterContext = React.useContext(DataRouterContext);
793-
invariant(
794-
dataRouterContext,
795-
"useSubmitImpl must be used within a Data Router"
796-
);
797-
let { router } = dataRouterContext;
821+
let { router } = useDataRouterContext(DataRouterHook.UseSubmitImpl);
798822
let defaultAction = useFormAction();
799823

800824
return React.useCallback(
@@ -909,9 +933,7 @@ export type FetcherWithComponents<TData> = Fetcher<TData> & {
909933
* for any interaction that stays on the same page.
910934
*/
911935
export function useFetcher<TData = any>(): FetcherWithComponents<TData> {
912-
let dataRouterContext = React.useContext(DataRouterContext);
913-
invariant(dataRouterContext, `useFetcher must be used within a Data Router`);
914-
let { router } = dataRouterContext;
936+
let { router } = useDataRouterContext(DataRouterHook.UseFetcher);
915937

916938
let route = React.useContext(RouteContext);
917939
invariant(route, `useFetcher must be used inside a RouteContext`);
@@ -967,8 +989,7 @@ export function useFetcher<TData = any>(): FetcherWithComponents<TData> {
967989
* routes that need to provide pending/optimistic UI regarding the fetch.
968990
*/
969991
export function useFetchers(): Fetcher[] {
970-
let state = React.useContext(DataRouterStateContext);
971-
invariant(state, `useFetchers must be used within a DataRouterStateContext`);
992+
let state = useDataRouterState(DataRouterStateHook.UseFetchers);
972993
return [...state.fetchers.values()];
973994
}
974995

@@ -985,22 +1006,13 @@ function useScrollRestoration({
9851006
getKey?: GetScrollRestorationKeyFunction;
9861007
storageKey?: string;
9871008
} = {}) {
1009+
let { router } = useDataRouterContext(DataRouterHook.UseScrollRestoration);
1010+
let { restoreScrollPosition, preventScrollReset } = useDataRouterState(
1011+
DataRouterStateHook.UseScrollRestoration
1012+
);
9881013
let location = useLocation();
9891014
let matches = useMatches();
9901015
let navigation = useNavigation();
991-
let dataRouterContext = React.useContext(DataRouterContext);
992-
invariant(
993-
dataRouterContext,
994-
"useScrollRestoration must be used within a DataRouterContext"
995-
);
996-
let { router } = dataRouterContext;
997-
let state = React.useContext(DataRouterStateContext);
998-
999-
invariant(
1000-
router != null && state != null,
1001-
"useScrollRestoration must be used within a DataRouterStateContext"
1002-
);
1003-
let { restoreScrollPosition, preventScrollReset } = state;
10041016

10051017
// Trigger manual scroll restoration while we're active
10061018
React.useEffect(() => {

0 commit comments

Comments
 (0)