@octanejs/remix-router 0.1.0
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.
- package/LICENSE +21 -0
- package/README.md +90 -0
- package/package.json +53 -0
- package/src/dom.ts +16 -0
- package/src/index.ts +267 -0
- package/src/internal.ts +37 -0
- package/src/lib/Await.tsrx +145 -0
- package/src/lib/Await.tsrx.d.ts +6 -0
- package/src/lib/DefaultErrorComponent.tsrx +47 -0
- package/src/lib/DefaultErrorComponent.tsrx.d.ts +2 -0
- package/src/lib/RenderErrorBoundary.tsrx +117 -0
- package/src/lib/RenderErrorBoundary.tsrx.d.ts +14 -0
- package/src/lib/actions.ts +90 -0
- package/src/lib/components/MemoryRouter.tsrx +42 -0
- package/src/lib/components/MemoryRouter.tsrx.d.ts +10 -0
- package/src/lib/components/Navigate.ts +60 -0
- package/src/lib/components/Router.tsrx +88 -0
- package/src/lib/components/Router.tsrx.d.ts +13 -0
- package/src/lib/components/RouterProvider.tsrx +320 -0
- package/src/lib/components/RouterProvider.tsrx.d.ts +21 -0
- package/src/lib/components/Routes.tsrx +53 -0
- package/src/lib/components/Routes.tsrx.d.ts +7 -0
- package/src/lib/components/routes-collector.ts +317 -0
- package/src/lib/components/utils.ts +171 -0
- package/src/lib/components/with-props.ts +72 -0
- package/src/lib/context.ts +129 -0
- package/src/lib/dom/Form.tsrx +76 -0
- package/src/lib/dom/Form.tsrx.d.ts +22 -0
- package/src/lib/dom/Link.tsrx +91 -0
- package/src/lib/dom/Link.tsrx.d.ts +22 -0
- package/src/lib/dom/NavLink.tsrx +118 -0
- package/src/lib/dom/NavLink.tsrx.d.ts +33 -0
- package/src/lib/dom/dom.ts +344 -0
- package/src/lib/dom/hooks.ts +93 -0
- package/src/lib/dom/lib.ts +822 -0
- package/src/lib/dom/routers.tsrx +104 -0
- package/src/lib/dom/routers.tsrx.d.ts +23 -0
- package/src/lib/dom/server.tsrx +350 -0
- package/src/lib/dom/server.tsrx.d.ts +25 -0
- package/src/lib/errors.ts +84 -0
- package/src/lib/framework-stubs.ts +103 -0
- package/src/lib/hooks.ts +1797 -0
- package/src/lib/href.ts +71 -0
- package/src/lib/react-types.ts +10 -0
- package/src/lib/router/history.ts +763 -0
- package/src/lib/router/instrumentation.ts +496 -0
- package/src/lib/router/links.ts +192 -0
- package/src/lib/router/router.ts +7165 -0
- package/src/lib/router/server-runtime-types.ts +12 -0
- package/src/lib/router/url.ts +8 -0
- package/src/lib/router/utils.ts +2179 -0
- package/src/lib/server-runtime/cookies.ts +240 -0
- package/src/lib/server-runtime/crypto.ts +55 -0
- package/src/lib/server-runtime/mode.ts +16 -0
- package/src/lib/server-runtime/sessions/cookieStorage.ts +54 -0
- package/src/lib/server-runtime/sessions/memoryStorage.ts +59 -0
- package/src/lib/server-runtime/sessions.ts +291 -0
- package/src/lib/server-runtime/warnings.ts +10 -0
- package/src/lib/types/future.ts +16 -0
- package/src/lib/types/params.ts +8 -0
- package/src/lib/types/register.ts +39 -0
- package/src/lib/types/route-module.ts +17 -0
- package/src/lib/types/utils.ts +37 -0
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
// RouterProvider + DataRoutes + Outlet — transcribed from react-router@7.18.1
|
|
2
|
+
// lib/components.tsx onto octane. Structure preserved verbatim: useState for
|
|
3
|
+
// router state + layout-effect subscription (early enough for render-driven
|
|
4
|
+
// redirects; subscribe() replays a pre-attach notification), useOptimistic for
|
|
5
|
+
// the optimistic in-flight state, and the three commit paths — plain
|
|
6
|
+
// startTransition, flushSync + startViewTransition, and startTransition INSIDE
|
|
7
|
+
// the deferred startViewTransition callback (which is exactly what octane's
|
|
8
|
+
// transition-at-commit constraint requires — see tanstack-router's
|
|
9
|
+
// src/router.ts:19-53 for the background). jsdom has no startViewTransition,
|
|
10
|
+
// so the VT paths are dormant under tests until Phase E exercises them.
|
|
11
|
+
//
|
|
12
|
+
// Octane substitutions: hooks from 'octane' (useOptimistic exists — upstream's
|
|
13
|
+
// useOptimisticSafe feature-detection collapses to a direct call), memo() for
|
|
14
|
+
// MemoizedDataRoutes, and the RSC branch dropped (RSCRouterContext is
|
|
15
|
+
// hardwired false in this port).
|
|
16
|
+
import {
|
|
17
|
+
memo,
|
|
18
|
+
startTransition,
|
|
19
|
+
useCallback,
|
|
20
|
+
useEffect,
|
|
21
|
+
useLayoutEffect,
|
|
22
|
+
useMemo,
|
|
23
|
+
useOptimistic,
|
|
24
|
+
useRef,
|
|
25
|
+
useState,
|
|
26
|
+
} from 'octane';
|
|
27
|
+
import {
|
|
28
|
+
DataRouterContext,
|
|
29
|
+
DataRouterStateContext,
|
|
30
|
+
FetchersContext,
|
|
31
|
+
ViewTransitionContext,
|
|
32
|
+
} from '../context.ts';
|
|
33
|
+
import { getRoutePattern } from '../router/utils.ts';
|
|
34
|
+
import { useOutlet, useRoutesImpl } from '../hooks.ts';
|
|
35
|
+
import { Router } from './Router.tsrx';
|
|
36
|
+
import { Deferred, getOptimisticRouterState, warnOnce } from './utils.ts';
|
|
37
|
+
|
|
38
|
+
export function Outlet(props) {
|
|
39
|
+
return useOutlet(props.context);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function DataRoutes(props) {
|
|
43
|
+
return useRoutesImpl(props.routes, undefined, {
|
|
44
|
+
manifest: props.manifest,
|
|
45
|
+
state: props.state,
|
|
46
|
+
isStatic: props.isStatic,
|
|
47
|
+
onError: props.onError,
|
|
48
|
+
future: props.future,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Memoize to avoid re-renders when updating `ViewTransitionContext`
|
|
53
|
+
const MemoizedDataRoutes = memo(DataRoutes);
|
|
54
|
+
|
|
55
|
+
export function RouterProvider(props) @{
|
|
56
|
+
const { router, flushSync: reactDomFlushSyncImpl, onError, useTransitions } = props;
|
|
57
|
+
|
|
58
|
+
const [_state, setStateImpl] = useState(router.state);
|
|
59
|
+
const [state, setOptimisticState] = useOptimistic(_state);
|
|
60
|
+
const [pendingState, setPendingState] = useState(undefined);
|
|
61
|
+
const [vtContext, setVtContext] = useState({ isTransitioning: false });
|
|
62
|
+
const [renderDfd, setRenderDfd] = useState(undefined);
|
|
63
|
+
const [transition, setTransition] = useState(undefined);
|
|
64
|
+
const [interruption, setInterruption] = useState(undefined);
|
|
65
|
+
const fetcherData = useRef(new Map());
|
|
66
|
+
|
|
67
|
+
const setState = useCallback((
|
|
68
|
+
newState,
|
|
69
|
+
{ deletedFetchers, newErrors, flushSync, viewTransitionOpts },
|
|
70
|
+
) => {
|
|
71
|
+
// Send router errors through onError
|
|
72
|
+
if (newErrors && onError) {
|
|
73
|
+
Object.values(newErrors).forEach(
|
|
74
|
+
(error) => onError(error, {
|
|
75
|
+
location: newState.location,
|
|
76
|
+
params: newState.matches[0]?.params ?? {},
|
|
77
|
+
pattern: getRoutePattern(newState.matches),
|
|
78
|
+
}),
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
newState.fetchers.forEach((fetcher, key) => {
|
|
83
|
+
if (fetcher.data !== undefined) {
|
|
84
|
+
fetcherData.current.set(key, fetcher.data);
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
deletedFetchers.forEach((key) => fetcherData.current.delete(key));
|
|
88
|
+
|
|
89
|
+
warnOnce(
|
|
90
|
+
flushSync === false || reactDomFlushSyncImpl != null,
|
|
91
|
+
'You provided the `flushSync` option to a router update, ' +
|
|
92
|
+
'but you are not using the `<RouterProvider>` from `@octanejs/remix-router/dom` ' +
|
|
93
|
+
'so `flushSync()` is unavailable. Please update your app ' +
|
|
94
|
+
'to `import { RouterProvider } from "@octanejs/remix-router/dom"` ' +
|
|
95
|
+
'to use the `flushSync` option.',
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
const isViewTransitionAvailable =
|
|
99
|
+
router.window != null && router.window.document != null &&
|
|
100
|
+
typeof router.window.document.startViewTransition === 'function';
|
|
101
|
+
|
|
102
|
+
warnOnce(
|
|
103
|
+
viewTransitionOpts == null || isViewTransitionAvailable,
|
|
104
|
+
'You provided the `viewTransition` option to a router update, ' +
|
|
105
|
+
'but you do not appear to be running in a DOM environment as ' +
|
|
106
|
+
'`window.startViewTransition` is not available.',
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
// If this isn't a view transition or it's not available in this browser,
|
|
110
|
+
// just update and be done with it
|
|
111
|
+
if (!viewTransitionOpts || !isViewTransitionAvailable) {
|
|
112
|
+
if (reactDomFlushSyncImpl && flushSync) {
|
|
113
|
+
reactDomFlushSyncImpl(() => setStateImpl(newState));
|
|
114
|
+
} else if (useTransitions === false) {
|
|
115
|
+
setStateImpl(newState);
|
|
116
|
+
} else {
|
|
117
|
+
startTransition(() => {
|
|
118
|
+
if (useTransitions === true) {
|
|
119
|
+
setOptimisticState((s) => getOptimisticRouterState(s, newState));
|
|
120
|
+
}
|
|
121
|
+
setStateImpl(newState);
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// flushSync + startViewTransition
|
|
128
|
+
if (reactDomFlushSyncImpl && flushSync) {
|
|
129
|
+
// Flush through the context to mark DOM elements as transition=ing
|
|
130
|
+
reactDomFlushSyncImpl(() => {
|
|
131
|
+
// Cancel any pending transitions
|
|
132
|
+
if (transition) {
|
|
133
|
+
renderDfd?.resolve();
|
|
134
|
+
transition.skipTransition();
|
|
135
|
+
}
|
|
136
|
+
setVtContext({
|
|
137
|
+
isTransitioning: true,
|
|
138
|
+
flushSync: true,
|
|
139
|
+
currentLocation: viewTransitionOpts.currentLocation,
|
|
140
|
+
nextLocation: viewTransitionOpts.nextLocation,
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
// Update the DOM
|
|
145
|
+
const t = router.window.document.startViewTransition(() => {
|
|
146
|
+
reactDomFlushSyncImpl(() => setStateImpl(newState));
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
// Clean up after the animation completes
|
|
150
|
+
t.finished.finally(() => {
|
|
151
|
+
reactDomFlushSyncImpl(() => {
|
|
152
|
+
setRenderDfd(undefined);
|
|
153
|
+
setTransition(undefined);
|
|
154
|
+
setPendingState(undefined);
|
|
155
|
+
setVtContext({ isTransitioning: false });
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
reactDomFlushSyncImpl(() => setTransition(t));
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// startTransition + startViewTransition
|
|
164
|
+
if (transition) {
|
|
165
|
+
// Interrupting an in-progress transition, cancel and let everything flush
|
|
166
|
+
// out, and then kick off a new transition from the interruption state
|
|
167
|
+
renderDfd?.resolve();
|
|
168
|
+
transition.skipTransition();
|
|
169
|
+
setInterruption({
|
|
170
|
+
state: newState,
|
|
171
|
+
currentLocation: viewTransitionOpts.currentLocation,
|
|
172
|
+
nextLocation: viewTransitionOpts.nextLocation,
|
|
173
|
+
});
|
|
174
|
+
} else {
|
|
175
|
+
// Completed navigation update with opted-in view transitions, let 'er rip
|
|
176
|
+
setPendingState(newState);
|
|
177
|
+
setVtContext({
|
|
178
|
+
isTransitioning: true,
|
|
179
|
+
flushSync: false,
|
|
180
|
+
currentLocation: viewTransitionOpts.currentLocation,
|
|
181
|
+
nextLocation: viewTransitionOpts.nextLocation,
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
}, [
|
|
185
|
+
router.window,
|
|
186
|
+
reactDomFlushSyncImpl,
|
|
187
|
+
transition,
|
|
188
|
+
renderDfd,
|
|
189
|
+
useTransitions,
|
|
190
|
+
setOptimisticState,
|
|
191
|
+
onError,
|
|
192
|
+
]);
|
|
193
|
+
|
|
194
|
+
// Need to use a layout effect here so we are subscribed early enough to
|
|
195
|
+
// pick up on any render-driven redirects/navigations (useEffect/<Navigate>).
|
|
196
|
+
// If the router finished initializing (and emitted errors) before this
|
|
197
|
+
// subscriber was attached, `subscribe()` replays that notification so
|
|
198
|
+
// `onError` still fires for initial data load errors.
|
|
199
|
+
useLayoutEffect(() => router.subscribe(setState), [router, setState]);
|
|
200
|
+
|
|
201
|
+
// When we start a view transition, create a Deferred we can use for the
|
|
202
|
+
// eventual "completed" render
|
|
203
|
+
useEffect(() => {
|
|
204
|
+
if (vtContext.isTransitioning && !vtContext.flushSync) {
|
|
205
|
+
setRenderDfd(new Deferred());
|
|
206
|
+
}
|
|
207
|
+
}, [vtContext]);
|
|
208
|
+
|
|
209
|
+
// Once the deferred is created, kick off startViewTransition() to update the
|
|
210
|
+
// DOM and then wait on the Deferred to resolve (indicating the DOM update has
|
|
211
|
+
// happened)
|
|
212
|
+
useEffect(() => {
|
|
213
|
+
if (renderDfd && pendingState && router.window) {
|
|
214
|
+
const newState = pendingState;
|
|
215
|
+
const renderPromise = renderDfd.promise;
|
|
216
|
+
const t = router.window.document.startViewTransition(async () => {
|
|
217
|
+
if (useTransitions === false) {
|
|
218
|
+
setStateImpl(newState);
|
|
219
|
+
} else {
|
|
220
|
+
startTransition(() => {
|
|
221
|
+
if (useTransitions === true) {
|
|
222
|
+
setOptimisticState((s) => getOptimisticRouterState(s, newState));
|
|
223
|
+
}
|
|
224
|
+
setStateImpl(newState);
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
await renderPromise;
|
|
228
|
+
});
|
|
229
|
+
t.finished.finally(() => {
|
|
230
|
+
setRenderDfd(undefined);
|
|
231
|
+
setTransition(undefined);
|
|
232
|
+
setPendingState(undefined);
|
|
233
|
+
setVtContext({ isTransitioning: false });
|
|
234
|
+
});
|
|
235
|
+
setTransition(t);
|
|
236
|
+
}
|
|
237
|
+
}, [pendingState, renderDfd, router.window, useTransitions, setOptimisticState]);
|
|
238
|
+
|
|
239
|
+
// When the new location finally renders and is committed to the DOM, this
|
|
240
|
+
// effect will run to resolve the transition
|
|
241
|
+
useEffect(() => {
|
|
242
|
+
if (renderDfd && pendingState && state.location.key === pendingState.location.key) {
|
|
243
|
+
renderDfd.resolve();
|
|
244
|
+
}
|
|
245
|
+
}, [renderDfd, transition, state.location, pendingState]);
|
|
246
|
+
|
|
247
|
+
// If we get interrupted with a new navigation during a transition, we skip
|
|
248
|
+
// the active transition, let it cleanup, then kick it off again here
|
|
249
|
+
useEffect(() => {
|
|
250
|
+
if (!vtContext.isTransitioning && interruption) {
|
|
251
|
+
setPendingState(interruption.state);
|
|
252
|
+
setVtContext({
|
|
253
|
+
isTransitioning: true,
|
|
254
|
+
flushSync: false,
|
|
255
|
+
currentLocation: interruption.currentLocation,
|
|
256
|
+
nextLocation: interruption.nextLocation,
|
|
257
|
+
});
|
|
258
|
+
setInterruption(undefined);
|
|
259
|
+
}
|
|
260
|
+
}, [vtContext.isTransitioning, interruption]);
|
|
261
|
+
|
|
262
|
+
const navigator = useMemo(() => {
|
|
263
|
+
return {
|
|
264
|
+
createHref: router.createHref,
|
|
265
|
+
encodeLocation: router.encodeLocation,
|
|
266
|
+
go: (n) => router.navigate(n),
|
|
267
|
+
push: (to, state2, opts) => router.navigate(to, {
|
|
268
|
+
state: state2,
|
|
269
|
+
preventScrollReset: opts?.preventScrollReset,
|
|
270
|
+
}),
|
|
271
|
+
replace: (to, state2, opts) => router.navigate(to, {
|
|
272
|
+
replace: true,
|
|
273
|
+
state: state2,
|
|
274
|
+
preventScrollReset: opts?.preventScrollReset,
|
|
275
|
+
}),
|
|
276
|
+
};
|
|
277
|
+
}, [router]);
|
|
278
|
+
|
|
279
|
+
const basename = router.basename || '/';
|
|
280
|
+
|
|
281
|
+
const dataRouterContext = useMemo(
|
|
282
|
+
() => ({
|
|
283
|
+
router,
|
|
284
|
+
navigator,
|
|
285
|
+
static: false,
|
|
286
|
+
basename,
|
|
287
|
+
onError,
|
|
288
|
+
}),
|
|
289
|
+
[router, navigator, basename, onError],
|
|
290
|
+
);
|
|
291
|
+
|
|
292
|
+
// The fragment shape mirrors upstream (kept for useId stability parity with
|
|
293
|
+
// the server-rendered StaticRouterProvider in a later phase).
|
|
294
|
+
<>
|
|
295
|
+
<DataRouterContext.Provider value={dataRouterContext}>
|
|
296
|
+
<DataRouterStateContext.Provider value={state}>
|
|
297
|
+
<FetchersContext.Provider value={fetcherData.current}>
|
|
298
|
+
<ViewTransitionContext.Provider value={vtContext}>
|
|
299
|
+
<Router
|
|
300
|
+
basename={basename}
|
|
301
|
+
location={state.location}
|
|
302
|
+
navigationType={state.historyAction}
|
|
303
|
+
navigator={navigator}
|
|
304
|
+
useTransitions={useTransitions}
|
|
305
|
+
>
|
|
306
|
+
<MemoizedDataRoutes
|
|
307
|
+
routes={router.routes}
|
|
308
|
+
manifest={router.manifest}
|
|
309
|
+
future={router.future}
|
|
310
|
+
state={state}
|
|
311
|
+
isStatic={false}
|
|
312
|
+
onError={onError}
|
|
313
|
+
/>
|
|
314
|
+
</Router>
|
|
315
|
+
</ViewTransitionContext.Provider>
|
|
316
|
+
</FetchersContext.Provider>
|
|
317
|
+
</DataRouterStateContext.Provider>
|
|
318
|
+
</DataRouterContext.Provider>
|
|
319
|
+
</>
|
|
320
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Type declaration for the .tsrx component (resolved by relative path).
|
|
2
|
+
import type { Router as DataRouter } from '../router/router';
|
|
3
|
+
import type { ClientOnErrorFunction } from '../context';
|
|
4
|
+
|
|
5
|
+
export declare const RouterProvider: (props: {
|
|
6
|
+
router: DataRouter;
|
|
7
|
+
flushSync?: (fn: () => unknown) => undefined;
|
|
8
|
+
onError?: ClientOnErrorFunction;
|
|
9
|
+
useTransitions?: boolean;
|
|
10
|
+
}) => unknown;
|
|
11
|
+
|
|
12
|
+
export declare const Outlet: (props: { context?: unknown }) => unknown;
|
|
13
|
+
|
|
14
|
+
export declare const DataRoutes: (props: {
|
|
15
|
+
routes: any[];
|
|
16
|
+
manifest: any;
|
|
17
|
+
future: any;
|
|
18
|
+
state: any;
|
|
19
|
+
isStatic: boolean;
|
|
20
|
+
onError?: ClientOnErrorFunction;
|
|
21
|
+
}) => unknown;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// Routes — transcribed from react-router@7.18.1 lib/components.tsx, hybrid
|
|
2
|
+
// per docs/remix-router-port-plan.md §2a:
|
|
3
|
+
// - descriptor children (createRoutesFromElements results, `{expr}` arrays,
|
|
4
|
+
// prop-position elements) go through createRoutesFromChildren verbatim;
|
|
5
|
+
// - the natural `.tsrx` block-children form (`<Routes><Route/></Routes>`
|
|
6
|
+
// compiles children to an opaque render block) goes through the
|
|
7
|
+
// registration collector (see routes-collector.ts): the block renders
|
|
8
|
+
// invisibly under the collector context, each <Route> registers, and the
|
|
9
|
+
// collected config renders through useRoutes on the next pre-paint pass.
|
|
10
|
+
import { isChildrenBlock, useMemo, useRef, useState } from 'octane';
|
|
11
|
+
import { useRoutes } from '../hooks.ts';
|
|
12
|
+
import {
|
|
13
|
+
RoutesCollectorContext,
|
|
14
|
+
createCollector,
|
|
15
|
+
createRoutesFromChildren,
|
|
16
|
+
} from './routes-collector.ts';
|
|
17
|
+
|
|
18
|
+
export function Routes(props) @{
|
|
19
|
+
@if (props.children != null && isChildrenBlock(props.children)) {
|
|
20
|
+
<BlockRoutes children={props.children} location={props.location} />
|
|
21
|
+
} @else {
|
|
22
|
+
<ElementRoutes children={props.children} location={props.location} />
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function ElementRoutes(props) {
|
|
27
|
+
return useRoutes(createRoutesFromChildren(props.children), props.location);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function CollectedRoutes(props) {
|
|
31
|
+
return useRoutes(props.routes, props.location);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function BlockRoutes(props) @{
|
|
35
|
+
const [version, setVersion] = useState(0);
|
|
36
|
+
const collectorRef = useRef(null);
|
|
37
|
+
if (collectorRef.current === null) {
|
|
38
|
+
collectorRef.current = createCollector(() => setVersion((v) => v + 1));
|
|
39
|
+
}
|
|
40
|
+
// Registrations land in the children's layout effects (pre-paint); the
|
|
41
|
+
// version bump re-renders with the finalized config, so the first paint
|
|
42
|
+
// already shows the matched route.
|
|
43
|
+
const routes = useMemo(() => collectorRef.current.collect(), [version]);
|
|
44
|
+
|
|
45
|
+
<>
|
|
46
|
+
<RoutesCollectorContext.Provider
|
|
47
|
+
value={collectorRef.current}
|
|
48
|
+
>{props.children}</RoutesCollectorContext.Provider>
|
|
49
|
+
@if (version > 0) {
|
|
50
|
+
<CollectedRoutes routes={routes} location={props.location} />
|
|
51
|
+
}
|
|
52
|
+
</>
|
|
53
|
+
}
|