react-router 6.3.0 → 6.4.0-pre.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/.eslintrc +12 -0
  2. package/CHANGELOG.md +8 -0
  3. package/__tests__/.eslintrc +8 -0
  4. package/__tests__/DataMemoryRouter-test.tsx +1902 -0
  5. package/__tests__/Route-test.tsx +45 -0
  6. package/__tests__/Router-basename-test.tsx +110 -0
  7. package/__tests__/Router-test.tsx +62 -0
  8. package/__tests__/Routes-location-test.tsx +69 -0
  9. package/__tests__/Routes-test.tsx +148 -0
  10. package/__tests__/__snapshots__/route-matching-test.tsx.snap +197 -0
  11. package/__tests__/absolute-path-matching-test.tsx +61 -0
  12. package/__tests__/createRoutesFromChildren-test.tsx +189 -0
  13. package/__tests__/descendant-routes-params-test.tsx +67 -0
  14. package/__tests__/descendant-routes-splat-matching-test.tsx +241 -0
  15. package/__tests__/descendant-routes-warning-test.tsx +140 -0
  16. package/__tests__/generatePath-test.tsx +45 -0
  17. package/__tests__/gh-issue-8127-test.tsx +32 -0
  18. package/__tests__/gh-issue-8165-test.tsx +97 -0
  19. package/__tests__/greedy-matching-test.tsx +89 -0
  20. package/__tests__/index-routes-test.tsx +24 -0
  21. package/__tests__/layout-routes-test.tsx +283 -0
  22. package/__tests__/matchPath-test.tsx +335 -0
  23. package/__tests__/matchRoutes-test.tsx +144 -0
  24. package/__tests__/navigate-test.tsx +49 -0
  25. package/__tests__/params-decode-test.tsx +36 -0
  26. package/__tests__/path-matching-test.tsx +270 -0
  27. package/__tests__/resolvePath-test.tsx +50 -0
  28. package/__tests__/route-depth-order-matching-test.tsx +135 -0
  29. package/__tests__/route-matching-test.tsx +164 -0
  30. package/__tests__/same-component-lifecycle-test.tsx +57 -0
  31. package/__tests__/setup.ts +15 -0
  32. package/__tests__/useHref-basename-test.tsx +351 -0
  33. package/__tests__/useHref-test.tsx +287 -0
  34. package/__tests__/useLocation-test.tsx +29 -0
  35. package/__tests__/useMatch-test.tsx +137 -0
  36. package/__tests__/useNavigate-test.tsx +100 -0
  37. package/__tests__/useOutlet-test.tsx +355 -0
  38. package/__tests__/useParams-test.tsx +212 -0
  39. package/__tests__/useResolvedPath-test.tsx +109 -0
  40. package/__tests__/useRoutes-test.tsx +122 -0
  41. package/__tests__/utils/renderStrict.tsx +21 -0
  42. package/__tests__/utils/waitForRedirect.tsx +5 -0
  43. package/index.ts +187 -0
  44. package/jest-transformer.js +10 -0
  45. package/jest.config.js +10 -0
  46. package/lib/components.tsx +491 -0
  47. package/lib/context.ts +96 -0
  48. package/lib/hooks.tsx +689 -0
  49. package/lib/use-sync-external-store-shim/index.ts +31 -0
  50. package/lib/use-sync-external-store-shim/useSyncExternalStoreShimClient.ts +153 -0
  51. package/lib/use-sync-external-store-shim/useSyncExternalStoreShimServer.ts +20 -0
  52. package/node-main.js +7 -0
  53. package/package.json +7 -4
  54. package/tsconfig.json +20 -0
  55. package/LICENSE.md +0 -22
  56. package/index.d.ts +0 -14
  57. package/index.js +0 -941
  58. package/index.js.map +0 -1
  59. package/lib/components.d.ts +0 -110
  60. package/lib/context.d.ts +0 -31
  61. package/lib/hooks.d.ts +0 -99
  62. package/lib/router.d.ts +0 -120
  63. package/main.js +0 -19
  64. package/react-router.development.js +0 -895
  65. package/react-router.development.js.map +0 -1
  66. package/react-router.production.min.js +0 -12
  67. package/react-router.production.min.js.map +0 -1
  68. package/umd/react-router.development.js +0 -990
  69. package/umd/react-router.development.js.map +0 -1
  70. package/umd/react-router.production.min.js +0 -12
  71. package/umd/react-router.production.min.js.map +0 -1
@@ -0,0 +1,491 @@
1
+ import * as React from "react";
2
+ import type {
3
+ HydrationState,
4
+ InitialEntry,
5
+ Location,
6
+ MemoryHistory,
7
+ RouteMatch,
8
+ RouteObject,
9
+ Router as DataRouter,
10
+ RouterState,
11
+ To,
12
+ } from "@remix-run/router";
13
+ import {
14
+ Action as NavigationType,
15
+ createMemoryHistory,
16
+ createMemoryRouter,
17
+ invariant,
18
+ normalizePathname,
19
+ parsePath,
20
+ stripBasename,
21
+ warning,
22
+ } from "@remix-run/router";
23
+ import { useSyncExternalStore as useSyncExternalStoreShim } from "./use-sync-external-store-shim";
24
+
25
+ import {
26
+ LocationContext,
27
+ NavigationContext,
28
+ Navigator,
29
+ DataRouterContext,
30
+ DataRouterStateContext,
31
+ } from "./context";
32
+ import {
33
+ useInRouterContext,
34
+ useNavigate,
35
+ useOutlet,
36
+ useRoutes,
37
+ _renderMatches,
38
+ } from "./hooks";
39
+
40
+ // Module-scoped singleton to hold the router. Extracted from the React lifecycle
41
+ // to avoid issues w.r.t. dual initialization fetches in concurrent rendering.
42
+ // Data router apps are expected to have a static route tree and are not intended
43
+ // to be unmounted/remounted at runtime.
44
+ let routerSingleton: DataRouter;
45
+
46
+ /**
47
+ * Unit-testing-only function to reset the router between tests
48
+ * @private
49
+ */
50
+ export function _resetModuleScope() {
51
+ // @ts-expect-error
52
+ routerSingleton = null;
53
+ }
54
+
55
+ /**
56
+ * @private
57
+ */
58
+ export function useRenderDataRouter({
59
+ children,
60
+ fallbackElement,
61
+ routes,
62
+ createRouter,
63
+ }: {
64
+ children?: React.ReactNode;
65
+ fallbackElement?: React.ReactNode;
66
+ routes?: RouteObject[];
67
+ createRouter: (routes: RouteObject[]) => DataRouter;
68
+ }): React.ReactElement {
69
+ if (!routerSingleton) {
70
+ routerSingleton = createRouter(
71
+ routes || createRoutesFromChildren(children)
72
+ ).initialize();
73
+ }
74
+ let router = routerSingleton;
75
+
76
+ // Sync router state to our component state to force re-renders
77
+ let state: RouterState = useSyncExternalStoreShim(
78
+ router.subscribe,
79
+ () => router.state
80
+ );
81
+
82
+ let navigator = React.useMemo((): Navigator => {
83
+ return {
84
+ createHref: router.createHref,
85
+ go: (n) => router.navigate(n),
86
+ push: (to, state, opts) =>
87
+ router.navigate(to, { state, resetScroll: opts?.resetScroll }),
88
+ replace: (to, state, opts) =>
89
+ router.navigate(to, {
90
+ replace: true,
91
+ state,
92
+ resetScroll: opts?.resetScroll,
93
+ }),
94
+ };
95
+ }, [router]);
96
+
97
+ if (!state.initialized) {
98
+ return <>{fallbackElement}</>;
99
+ }
100
+
101
+ return (
102
+ <DataRouterContext.Provider value={router}>
103
+ <DataRouterStateContext.Provider value={state}>
104
+ <Router
105
+ location={state.location}
106
+ navigationType={state.historyAction}
107
+ navigator={navigator}
108
+ >
109
+ <DataRoutes routes={routes} children={children} />
110
+ </Router>
111
+ </DataRouterStateContext.Provider>
112
+ </DataRouterContext.Provider>
113
+ );
114
+ }
115
+
116
+ export interface DataMemoryRouterProps {
117
+ children?: React.ReactNode;
118
+ initialEntries?: InitialEntry[];
119
+ initialIndex?: number;
120
+ hydrationData?: HydrationState;
121
+ fallbackElement?: React.ReactNode;
122
+ routes?: RouteObject[];
123
+ }
124
+
125
+ export function DataMemoryRouter({
126
+ children,
127
+ initialEntries,
128
+ initialIndex,
129
+ hydrationData,
130
+ fallbackElement,
131
+ routes,
132
+ }: DataMemoryRouterProps): React.ReactElement {
133
+ return useRenderDataRouter({
134
+ children,
135
+ fallbackElement,
136
+ routes,
137
+ createRouter: (routes) =>
138
+ createMemoryRouter({
139
+ initialEntries,
140
+ initialIndex,
141
+ routes,
142
+ hydrationData,
143
+ }),
144
+ });
145
+ }
146
+
147
+ export interface MemoryRouterProps {
148
+ basename?: string;
149
+ children?: React.ReactNode;
150
+ initialEntries?: InitialEntry[];
151
+ initialIndex?: number;
152
+ }
153
+
154
+ /**
155
+ * A <Router> that stores all entries in memory.
156
+ *
157
+ * @see https://reactrouter.com/docs/en/v6/routers/memory-router
158
+ */
159
+ export function MemoryRouter({
160
+ basename,
161
+ children,
162
+ initialEntries,
163
+ initialIndex,
164
+ }: MemoryRouterProps): React.ReactElement {
165
+ let historyRef = React.useRef<MemoryHistory>();
166
+ if (historyRef.current == null) {
167
+ historyRef.current = createMemoryHistory({
168
+ initialEntries,
169
+ initialIndex,
170
+ v5Compat: true,
171
+ });
172
+ }
173
+
174
+ let history = historyRef.current;
175
+ let [state, setState] = React.useState({
176
+ action: history.action,
177
+ location: history.location,
178
+ });
179
+
180
+ React.useLayoutEffect(() => history.listen(setState), [history]);
181
+
182
+ return (
183
+ <Router
184
+ basename={basename}
185
+ children={children}
186
+ location={state.location}
187
+ navigationType={state.action}
188
+ navigator={history}
189
+ />
190
+ );
191
+ }
192
+
193
+ export interface NavigateProps {
194
+ to: To;
195
+ replace?: boolean;
196
+ state?: any;
197
+ }
198
+
199
+ /**
200
+ * Changes the current location.
201
+ *
202
+ * Note: This API is mostly useful in React.Component subclasses that are not
203
+ * able to use hooks. In functional components, we recommend you use the
204
+ * `useNavigate` hook instead.
205
+ *
206
+ * @see https://reactrouter.com/docs/en/v6/components/navigate
207
+ */
208
+ export function Navigate({ to, replace, state }: NavigateProps): null {
209
+ invariant(
210
+ useInRouterContext(),
211
+ // TODO: This error is probably because they somehow have 2 versions of
212
+ // the router loaded. We can help them understand how to avoid that.
213
+ `<Navigate> may be used only in the context of a <Router> component.`
214
+ );
215
+
216
+ warning(
217
+ !React.useContext(NavigationContext).static,
218
+ `<Navigate> must not be used on the initial render in a <StaticRouter>. ` +
219
+ `This is a no-op, but you should modify your code so the <Navigate> is ` +
220
+ `only ever rendered in response to some user interaction or state change.`
221
+ );
222
+
223
+ let navigate = useNavigate();
224
+ React.useEffect(() => {
225
+ navigate(to, { replace, state });
226
+ });
227
+
228
+ return null;
229
+ }
230
+
231
+ export interface OutletProps {
232
+ context?: unknown;
233
+ }
234
+
235
+ /**
236
+ * Renders the child route's element, if there is one.
237
+ *
238
+ * @see https://reactrouter.com/docs/en/v6/components/outlet
239
+ */
240
+ export function Outlet(props: OutletProps): React.ReactElement | null {
241
+ return useOutlet(props.context);
242
+ }
243
+
244
+ interface DataRouteProps {
245
+ id?: RouteObject["id"];
246
+ loader?: RouteObject["loader"];
247
+ action?: RouteObject["action"];
248
+ errorElement?: RouteObject["errorElement"];
249
+ shouldRevalidate?: RouteObject["shouldRevalidate"];
250
+ handle?: RouteObject["handle"];
251
+ }
252
+
253
+ export interface RouteProps extends DataRouteProps {
254
+ caseSensitive?: boolean;
255
+ children?: React.ReactNode;
256
+ element?: React.ReactNode | null;
257
+ index?: boolean;
258
+ path?: string;
259
+ }
260
+
261
+ export interface PathRouteProps extends DataRouteProps {
262
+ caseSensitive?: boolean;
263
+ children?: React.ReactNode;
264
+ element?: React.ReactNode | null;
265
+ index?: false;
266
+ path: string;
267
+ }
268
+
269
+ export interface LayoutRouteProps extends DataRouteProps {
270
+ children?: React.ReactNode;
271
+ element?: React.ReactNode | null;
272
+ }
273
+
274
+ export interface IndexRouteProps extends DataRouteProps {
275
+ element?: React.ReactNode | null;
276
+ index: true;
277
+ }
278
+
279
+ /**
280
+ * Declares an element that should be rendered at a certain URL path.
281
+ *
282
+ * @see https://reactrouter.com/docs/en/v6/components/route
283
+ */
284
+ export function Route(
285
+ _props: PathRouteProps | LayoutRouteProps | IndexRouteProps
286
+ ): React.ReactElement | null {
287
+ invariant(
288
+ false,
289
+ `A <Route> is only ever to be used as the child of <Routes> element, ` +
290
+ `never rendered directly. Please wrap your <Route> in a <Routes>.`
291
+ );
292
+ }
293
+
294
+ export interface RouterProps {
295
+ basename?: string;
296
+ children?: React.ReactNode;
297
+ location: Partial<Location> | string;
298
+ navigationType?: NavigationType;
299
+ navigator: Navigator;
300
+ static?: boolean;
301
+ }
302
+
303
+ /**
304
+ * Provides location context for the rest of the app.
305
+ *
306
+ * Note: You usually won't render a <Router> directly. Instead, you'll render a
307
+ * router that is more specific to your environment such as a <BrowserRouter>
308
+ * in web browsers or a <StaticRouter> for server rendering.
309
+ *
310
+ * @see https://reactrouter.com/docs/en/v6/routers/router
311
+ */
312
+ export function Router({
313
+ basename: basenameProp = "/",
314
+ children = null,
315
+ location: locationProp,
316
+ navigationType = NavigationType.Pop,
317
+ navigator,
318
+ static: staticProp = false,
319
+ }: RouterProps): React.ReactElement | null {
320
+ invariant(
321
+ !useInRouterContext(),
322
+ `You cannot render a <Router> inside another <Router>.` +
323
+ ` You should never have more than one in your app.`
324
+ );
325
+
326
+ let basename = normalizePathname(basenameProp);
327
+ let navigationContext = React.useMemo(
328
+ () => ({ basename, navigator, static: staticProp }),
329
+ [basename, navigator, staticProp]
330
+ );
331
+
332
+ if (typeof locationProp === "string") {
333
+ locationProp = parsePath(locationProp);
334
+ }
335
+
336
+ let {
337
+ pathname = "/",
338
+ search = "",
339
+ hash = "",
340
+ state = null,
341
+ key = "default",
342
+ } = locationProp;
343
+
344
+ let location = React.useMemo(() => {
345
+ let trailingPathname = stripBasename(pathname, basename);
346
+
347
+ if (trailingPathname == null) {
348
+ return null;
349
+ }
350
+
351
+ return {
352
+ pathname: trailingPathname,
353
+ search,
354
+ hash,
355
+ state,
356
+ key,
357
+ };
358
+ }, [basename, pathname, search, hash, state, key]);
359
+
360
+ warning(
361
+ location != null,
362
+ `<Router basename="${basename}"> is not able to match the URL ` +
363
+ `"${pathname}${search}${hash}" because it does not start with the ` +
364
+ `basename, so the <Router> won't render anything.`
365
+ );
366
+
367
+ if (location == null) {
368
+ return null;
369
+ }
370
+
371
+ return (
372
+ <NavigationContext.Provider value={navigationContext}>
373
+ <LocationContext.Provider
374
+ children={children}
375
+ value={{ location, navigationType }}
376
+ />
377
+ </NavigationContext.Provider>
378
+ );
379
+ }
380
+
381
+ export interface RoutesProps {
382
+ children?: React.ReactNode;
383
+ location?: Partial<Location> | string;
384
+ }
385
+
386
+ /**
387
+ * A container for a nested tree of <Route> elements that renders the branch
388
+ * that best matches the current location.
389
+ *
390
+ * @see https://reactrouter.com/docs/en/v6/components/routes
391
+ */
392
+ export function Routes({
393
+ children,
394
+ location,
395
+ }: RoutesProps): React.ReactElement | null {
396
+ return useRoutes(createRoutesFromChildren(children), location);
397
+ }
398
+
399
+ interface DataRoutesProps extends RoutesProps {
400
+ routes?: RouteObject[];
401
+ }
402
+
403
+ /**
404
+ * @private
405
+ * Used as an extension to <Routes> and accepts a manual `routes` array to be
406
+ * instead of using JSX children. Extracted to it's own component to avoid
407
+ * conditional usage of `useRoutes` if we have to render a `fallbackElement`
408
+ */
409
+ function DataRoutes({
410
+ children,
411
+ location,
412
+ routes,
413
+ }: DataRoutesProps): React.ReactElement | null {
414
+ return useRoutes(routes || createRoutesFromChildren(children), location);
415
+ }
416
+
417
+ ///////////////////////////////////////////////////////////////////////////////
418
+ // UTILS
419
+ ///////////////////////////////////////////////////////////////////////////////
420
+
421
+ /**
422
+ * Creates a route config from a React "children" object, which is usually
423
+ * either a `<Route>` element or an array of them. Used internally by
424
+ * `<Routes>` to create a route config from its children.
425
+ *
426
+ * @see https://reactrouter.com/docs/en/v6/utils/create-routes-from-children
427
+ */
428
+ export function createRoutesFromChildren(
429
+ children: React.ReactNode,
430
+ parentPath: number[] = []
431
+ ): RouteObject[] {
432
+ let routes: RouteObject[] = [];
433
+
434
+ React.Children.forEach(children, (element, index) => {
435
+ if (!React.isValidElement(element)) {
436
+ // Ignore non-elements. This allows people to more easily inline
437
+ // conditionals in their route config.
438
+ return;
439
+ }
440
+
441
+ if (element.type === React.Fragment) {
442
+ // Transparently support React.Fragment and its children.
443
+ routes.push.apply(
444
+ routes,
445
+ createRoutesFromChildren(element.props.children, parentPath)
446
+ );
447
+ return;
448
+ }
449
+
450
+ invariant(
451
+ element.type === Route,
452
+ `[${
453
+ typeof element.type === "string" ? element.type : element.type.name
454
+ }] is not a <Route> component. All component children of <Routes> must be a <Route> or <React.Fragment>`
455
+ );
456
+
457
+ let treePath = [...parentPath, index];
458
+ let route: RouteObject = {
459
+ id: element.props.id || treePath.join("-"),
460
+ caseSensitive: element.props.caseSensitive,
461
+ element: element.props.element,
462
+ index: element.props.index,
463
+ path: element.props.path,
464
+ loader: element.props.loader,
465
+ action: element.props.action,
466
+ errorElement: element.props.errorElement,
467
+ shouldRevalidate: element.props.shouldRevalidate,
468
+ handle: element.props.handle,
469
+ };
470
+
471
+ if (element.props.children) {
472
+ route.children = createRoutesFromChildren(
473
+ element.props.children,
474
+ treePath
475
+ );
476
+ }
477
+
478
+ routes.push(route);
479
+ });
480
+
481
+ return routes;
482
+ }
483
+
484
+ /**
485
+ * Renders the result of `matchRoutes()` into a React element.
486
+ */
487
+ export function renderMatches(
488
+ matches: RouteMatch[] | null
489
+ ): React.ReactElement | null {
490
+ return _renderMatches(matches);
491
+ }
package/lib/context.ts ADDED
@@ -0,0 +1,96 @@
1
+ import * as React from "react";
2
+ import type {
3
+ History,
4
+ Location,
5
+ RouteMatch,
6
+ Router,
7
+ To,
8
+ } from "@remix-run/router";
9
+ import { Action as NavigationType } from "@remix-run/router";
10
+
11
+ // Contexts for data routers
12
+ export const DataRouterContext = React.createContext<Router | null>(null);
13
+ if (__DEV__) {
14
+ DataRouterContext.displayName = "DataRouter";
15
+ }
16
+
17
+ export const DataRouterStateContext = React.createContext<
18
+ Router["state"] | null
19
+ >(null);
20
+ if (__DEV__) {
21
+ DataRouterStateContext.displayName = "DataRouterState";
22
+ }
23
+
24
+ export interface NavigateOptions {
25
+ replace?: boolean;
26
+ state?: any;
27
+ resetScroll?: boolean;
28
+ }
29
+
30
+ /**
31
+ * A Navigator is a "location changer"; it's how you get to different locations.
32
+ *
33
+ * Every history instance conforms to the Navigator interface, but the
34
+ * distinction is useful primarily when it comes to the low-level <Router> API
35
+ * where both the location and a navigator must be provided separately in order
36
+ * to avoid "tearing" that may occur in a suspense-enabled app if the action
37
+ * and/or location were to be read directly from the history instance.
38
+ */
39
+ export interface Navigator {
40
+ createHref: History["createHref"];
41
+ go: History["go"];
42
+ push(to: To, state?: any, opts?: NavigateOptions): void;
43
+ replace(to: To, state?: any, opts?: NavigateOptions): void;
44
+ }
45
+
46
+ interface NavigationContextObject {
47
+ basename: string;
48
+ navigator: Navigator;
49
+ static: boolean;
50
+ }
51
+
52
+ export const NavigationContext = React.createContext<NavigationContextObject>(
53
+ null!
54
+ );
55
+
56
+ if (__DEV__) {
57
+ NavigationContext.displayName = "Navigation";
58
+ }
59
+
60
+ interface LocationContextObject {
61
+ location: Location;
62
+ navigationType: NavigationType;
63
+ }
64
+
65
+ export const LocationContext = React.createContext<LocationContextObject>(
66
+ null!
67
+ );
68
+
69
+ if (__DEV__) {
70
+ LocationContext.displayName = "Location";
71
+ }
72
+
73
+ interface RouteContextObject {
74
+ outlet: React.ReactElement | null;
75
+ matches: RouteMatch[];
76
+ }
77
+
78
+ export const RouteContext = React.createContext<RouteContextObject>({
79
+ outlet: null,
80
+ matches: [],
81
+ });
82
+
83
+ if (__DEV__) {
84
+ RouteContext.displayName = "Route";
85
+ }
86
+
87
+ interface RouteContextObject {
88
+ outlet: React.ReactElement | null;
89
+ matches: RouteMatch[];
90
+ }
91
+
92
+ export const RouteErrorContext = React.createContext<any>(null);
93
+
94
+ if (__DEV__) {
95
+ RouteErrorContext.displayName = "RouteError";
96
+ }