clear-react-router 1.6.5 → 1.6.7

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/README.md CHANGED
@@ -47,7 +47,8 @@ Normalizes route configuration. Handles wildcard `*` routes, extracts dynamic pa
47
47
  | `loaderFallback` | `ReactElement \| () => ReactElement` | Loading fallback (for loader) |
48
48
  | `errorElement` | `ReactElement \| () => ReactElement` | Error fallback |
49
49
  | `staleTime` | `number` | Time in ms before cached data is considered stale and re-fetched in the background. If not provided, data never expires (cached forever) |
50
- | `children` | `RouteItem[]` | Nested routes |
50
+ | `actions` | `({ params, context, invalidate, setContext }) => Record<string, (formData: FormData) => unknown \| Promise<unknown>>` | Defines route actions for data mutations. Actions receive `FormData`, can update context via `setContext`, and can refresh loader data using the router-provided `invalidate`. |
51
+
51
52
 
52
53
  ### `RouterProvider`
53
54
 
@@ -56,7 +57,6 @@ The root component that provides routing context to the application. Place stati
56
57
  | Prop | Type | Default | Description |
57
58
  |------|------|---------|-------------|
58
59
  | `routes` | `RouteItem[]` | required | Array of route configurations |
59
- | `context` | `object` | `{}` | Initial context (user, theme, etc.) |
60
60
  | `children` | `ReactNode` | required | App content (must include `<Router />`) |
61
61
 
62
62
  ```tsx
@@ -86,6 +86,7 @@ Renders the current route's component. Must be placed inside `<RouterProvider>`.
86
86
  | `showFallbackOnAnimation` | `boolean \| undefined` | `false` | Show `loaderFallback` even when `isAnimated` is `true` (instead of spinner) |
87
87
  | `prefetch` | `'hover' \| 'render' \| 'viewport' \| 'none'` | `'hover'` | Default prefetch strategy for all `<Link>` components |
88
88
  | `hoverPrefetchDelay` | `number` | `150` | Delay in milliseconds before prefetching on hover (only for `'hover'` strategy) |
89
+ | `context` | `object` | `{}` | Initial context (user, theme, etc.) |
89
90
  | `errorBoundary` | `ComponentType<{ children: ReactNode }>` | `undefined` | Custom error boundary component for catching render errors in route components |
90
91
 
91
92
  ```tsx
@@ -219,6 +220,114 @@ const routes = createRouter([
219
220
  ]);
220
221
  ```
221
222
 
223
+ ## Route Actions
224
+
225
+ Defines route-specific actions for handling data mutations such as creating, updating, or deleting resources.
226
+
227
+ Actions are available through the `Form` component and the `useAction` hook. After a successful action, the current route is automatically invalidated, causing both `beforeLoad` and `loader` to run again in the background.
228
+
229
+ ```tsx
230
+ actions?: ({ context, params, invalidate, setContext }) => ({
231
+ save: async (formData) => {
232
+ await api.updatePost(params.id, formData);
233
+ },
234
+
235
+ remove: async () => {
236
+ await api.deletePost(params.id);
237
+ },
238
+ })
239
+ ```
240
+
241
+ #### Arguments
242
+
243
+ | Property | Type | Description |
244
+ | ------------ | --------------------------------------------------- | -------------------------------------------------------------------------------------------- |
245
+ | `context` | `Record<string, unknown>` | Current router context. |
246
+ | `params` | `Record<string, string>` | Route parameters. |
247
+ | `invalidate` | `(path?: string) => Promise<void>` | Invalidates the current route or a specific route, re-running its `beforeLoad` and `loader`. |
248
+ | `setContext` | `Dispatch<SetStateAction<Record<string, unknown>>>` | Updates the router context. |
249
+
250
+ #### Returns
251
+
252
+ A record where each key is an action name and each value is a function accepting a `FormData` instance.
253
+
254
+ These action names are referenced by both `Form` and `useAction`.
255
+
256
+ ```tsx
257
+ <Form action="save" />
258
+
259
+ const save = useAction('save');
260
+ ```
261
+
262
+
263
+ Actions can be executed declaratively with `<Form />` or imperatively with `useAction()`.
264
+
265
+ ## Form
266
+
267
+ `Form` automatically creates a `FormData` object, executes the specified route action, invalidates the current route, and optionally resets the form.
268
+
269
+ `isSubmitting` value available inside the `Form` component from the `useFormContext` hook
270
+
271
+ ```tsx
272
+ import { Form, useFormContext } from '../clear-router';
273
+
274
+ const SubmitButton = () => {
275
+ const {isSubmitting} = useFormContext()
276
+ return <button disabled={isSubmitting} type='submit'>Save</button>
277
+ }
278
+
279
+ <Form action="save" onSuccess={() => console.log('Saved')} onError={console.error}>
280
+ <input name="title" />
281
+ <SubmitButton />
282
+ </Form>
283
+
284
+ ```
285
+
286
+ ### Props
287
+
288
+ | Prop | Type | Description |
289
+ | ----------- | --------------------------- | -------------------------------------------------------------------------------- |
290
+ | `action` | `string` | Name of the route action to execute. |
291
+ | `onSuccess` | `(result: unknown) => void` | Called when the action completes successfully. Receives the action return value. |
292
+ | `onError` | `(error: unknown) => void` | Called when the action throws. |
293
+ | `autoReset` | `boolean` | Automatically resets the form after a successful submission. Default: `true`. |
294
+
295
+ During submission, `Form` exposes the current submission state through `useFormContext()`.
296
+
297
+ After a successful action:
298
+
299
+ * the current route is invalidated;
300
+ * `beforeLoad` is executed again;
301
+ * `loader` is executed again;
302
+ * fresh loader data becomes available.
303
+
304
+ ## useAction
305
+
306
+ `useAction` provides direct access to a route action without rendering a `<Form />`.
307
+
308
+ ```tsx
309
+ const save = useAction('save');
310
+
311
+ const handleClick = async () => {
312
+ const data = new FormData();
313
+
314
+ data.append('title', 'Hello');
315
+
316
+ await save(data);
317
+ };
318
+ ```
319
+
320
+ ```tsx
321
+ <button onClick={handleClick}>
322
+ Save
323
+ </button>
324
+ ```
325
+
326
+ `useAction` automatically invalidates the current route after a successful action, causing both `beforeLoad` and `loader` to run again in the background.
327
+
328
+ This hook is useful when the mutation is triggered programmatically, such as from dialogs, context menus, drag-and-drop interactions, keyboard shortcuts, or custom UI components.
329
+
330
+
222
331
  ### Error Boundaries
223
332
 
224
333
  You can provide a custom error boundary to catch rendering errors in route components. This is useful for preventing the entire app from crashing when a specific route fails to render.
@@ -240,13 +349,15 @@ const App = () => (
240
349
 
241
350
  ### `useNavigate()`
242
351
 
243
- Returns function to navigate programmatically. Accepts a string (pathname), an object with `pathname`, `search`, and `state`, or `-1` to go back.
352
+ Returns function to navigate programmatically. Accepts a string (pathname), an object of type Location, or `-1` to go back.
244
353
 
245
354
  ```tsx
355
+ type Location = { pathname: string; search?: string; state?: unknown }
356
+
246
357
  const navigate = useNavigate();
247
358
 
248
359
  navigate('/about'); // string
249
- navigate({ pathname: '/user/123', state: { from: 'home' } }); // object
360
+ navigate({ pathname: '/user/123', state: { from: 'home' } }); // Location
250
361
  navigate(-1); // go back
251
362
  ```
252
363
 
@@ -0,0 +1,9 @@
1
+ import { PropsWithChildren } from 'react';
2
+ type FormProps = {
3
+ action: string;
4
+ onSuccess?(arg: unknown): void;
5
+ onError?(arg: unknown): void;
6
+ autoReset?: boolean;
7
+ };
8
+ export declare const Form: ({ children, action: actionKey, onSuccess, onError, autoReset, }: PropsWithChildren<FormProps>) => import("react/jsx-runtime").JSX.Element;
9
+ export {};
@@ -1,2 +1,2 @@
1
1
  import { RouterProps } from '../types/global';
2
- export declare const Router: ({ isAnimated, animationDuration, spinner, preserveScroll, showFallbackOnAnimation, prefetch, hoverPrefetchDelay, errorBoundary: ErrorBoundary, }: RouterProps) => import("react/jsx-runtime").JSX.Element | null;
2
+ export declare const Router: ({ isAnimated, animationDuration, spinner, preserveScroll, showFallbackOnAnimation, prefetch, hoverPrefetchDelay, errorBoundary: ErrorBoundary, context: initialContext, }: RouterProps) => import("react/jsx-runtime").JSX.Element | null;
@@ -3,7 +3,6 @@ import { RouteItem } from '../types/global';
3
3
  type RouteProviderProps = {
4
4
  children: ReactNode;
5
5
  routes: RouteItem[];
6
- context?: Record<string, unknown>;
7
6
  };
8
- export declare const RouterProvider: ({ children, routes, context: initialContext }: RouteProviderProps) => import("react/jsx-runtime").JSX.Element;
7
+ export declare const RouterProvider: ({ children, routes }: RouteProviderProps) => import("react/jsx-runtime").JSX.Element;
9
8
  export {};
@@ -0,0 +1,4 @@
1
+ export type FormContextProps = {
2
+ isSubmitting: boolean;
3
+ };
4
+ export declare const FormContext: import("react").Context<FormContextProps>;
@@ -1,22 +1,7 @@
1
- import { BlockerState, LoaderState, Location, RouteItem, RouteItemData, UpdateBlockedRouteProps } from '../types/global';
2
- export type NavigationContextValue = {
3
- blockerState: BlockerState;
4
- routeItemData: RouteItemData;
5
- currentLoaderFallback: RouteItem['loaderFallback'];
6
- isLoading: boolean;
7
- loaderState: LoaderState;
8
- };
1
+ import { Location } from '../types/global';
9
2
  export type ActionsContextValue = {
10
3
  updateLocation(route: Location): Promise<void>;
11
- updateBlockedRoute(arg: UpdateBlockedRouteProps): void;
12
4
  prefetchLoader(arg: string): Promise<void>;
13
- setContext(arg: object): void;
14
- restoreScroll(): void;
15
- invalidate(path?: string): void;
16
- };
17
- export type DataContextValue = {
18
- context: Record<string, unknown>;
5
+ invalidate(path?: string): Promise<void>;
19
6
  };
20
7
  export declare const ActionsContext: import("react").Context<ActionsContextValue>;
21
- export declare const DataContext: import("react").Context<DataContextValue>;
22
- export declare const NavigationContext: import("react").Context<NavigationContextValue>;
@@ -0,0 +1 @@
1
+ export declare const useAction: (actionKey: string, onError?: (args: unknown) => void) => (formData: FormData) => Promise<void>;
@@ -1,4 +1,4 @@
1
- import type { BlockerState } from '../types/global.ts';
1
+ import { BlockerState } from '../types/global';
2
2
  type UseBlockerReturnValue = {
3
3
  state: BlockerState;
4
4
  process(): void;
@@ -0,0 +1 @@
1
+ export declare const useFormContext: () => import("../context/FormContext").FormContextProps;
@@ -1 +1 @@
1
- export declare const useInvalidate: () => (path?: string) => void;
1
+ export declare const useInvalidate: () => (path?: string) => Promise<void>;
@@ -1,11 +1,5 @@
1
- import { type Dispatch, type SetStateAction } from 'react';
2
1
  import type { LoaderState, RevalidateCacheArgs, RouteItem } from '../types/global';
3
- type UseLoaderParams = {
4
- routes: RouteItem[];
5
- context: Record<string, unknown>;
6
- setContext: Dispatch<SetStateAction<Record<string, unknown>>>;
7
- };
8
- export declare const useLoader: ({ routes, context, setContext }: UseLoaderParams) => {
2
+ export declare const useLoader: (routes: RouteItem[]) => {
9
3
  prefetchLoader: (pathname: string) => Promise<void>;
10
4
  revalidateCache: ({ routeItem, pathname }: RevalidateCacheArgs) => Promise<unknown>;
11
5
  isCacheItemFresh: ({ routeItem, pathname }: {
@@ -13,6 +7,5 @@ export declare const useLoader: ({ routes, context, setContext }: UseLoaderParam
13
7
  pathname: string;
14
8
  }) => boolean;
15
9
  loaderStateRef: import("react").RefObject<LoaderState>;
16
- clearTimestamp: (pathname: string) => void;
10
+ invalidate: (pathname?: string) => Promise<void>;
17
11
  };
18
- export {};
@@ -1,2 +1,2 @@
1
1
  import type { Location } from '../types/global';
2
- export declare const useNavigate: () => (arg: Location | string | number) => Promise<void>;
2
+ export declare const useNavigate: () => (arg: Location | string | -1) => Promise<void>;
@@ -0,0 +1,13 @@
1
+ import { RefObject } from 'react';
2
+ import { LoaderState, Location, RevalidateCacheArgs, RouteItem } from '../types/global';
3
+ type UseHandleNavigation = {
4
+ routes: RouteItem[];
5
+ revalidateCache(arg: RevalidateCacheArgs): Promise<unknown>;
6
+ isCacheItemFresh(arg: {
7
+ routeItem?: RouteItem;
8
+ pathname: string;
9
+ }): boolean;
10
+ loaderStateRef: RefObject<LoaderState>;
11
+ };
12
+ export declare const useNavigation: ({ routes, revalidateCache, isCacheItemFresh, loaderStateRef }: UseHandleNavigation) => (nextLocation: Location) => Promise<void>;
13
+ export {};
@@ -1,4 +1,4 @@
1
1
  export declare const useRouterContext: () => {
2
2
  context: Record<string, unknown>;
3
- setContext: (arg: object) => void;
3
+ setContext: (action: Record<string, unknown> | ((prevState: Record<string, unknown>) => Record<string, unknown>)) => void;
4
4
  };
@@ -1,3 +1 @@
1
- export declare const useNavigationState: () => import("../context/RouterProviderContext").NavigationContextValue;
2
1
  export declare const useRouterActions: () => import("../context/RouterProviderContext").ActionsContextValue;
3
- export declare const useRouterData: () => import("../context/RouterProviderContext").DataContextValue;
@@ -0,0 +1 @@
1
+ export declare const useSetInitialContext: (initialContext?: Record<string, unknown>) => void;
package/dist/index.d.ts CHANGED
@@ -12,6 +12,7 @@ export { useRouterContext } from './hooks/useRouterContext';
12
12
  export { useQueryParam } from './hooks/useQueryParam';
13
13
  export { useSearchParams } from './hooks/useSearchParams';
14
14
  export { useHistoricalTrail } from './hooks/useHistoricalTrail';
15
+ export { useFormContext } from './hooks/useFormContext';
15
16
  export { adapter } from './utils/adapter';
16
17
  export { createRouter } from './utils/utils';
17
18
  export type { RouteItem, BlockerState, Location, AdapterType, RouterProps } from './types/global';
package/dist/index.js CHANGED
@@ -4,8 +4,6 @@ var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).expor
4
4
  //#endregion
5
5
  //#region context/RouterProviderContext.ts
6
6
  var ActionsContext = createContext({});
7
- var DataContext = createContext({});
8
- var NavigationContext = createContext({});
9
7
  //#endregion
10
8
  //#region ../../node_modules/react/cjs/react-jsx-runtime.production.js
11
9
  /**
@@ -45,29 +43,14 @@ var require_react_jsx_runtime_production = /* @__PURE__ */ __commonJSMin(((expor
45
43
  var import_jsx_runtime = (/* @__PURE__ */ __commonJSMin(((exports, module) => {
46
44
  module.exports = require_react_jsx_runtime_production();
47
45
  })))();
48
- var Provider = ({ children, setContext, context, updateBlockedRoute, updateLocation, prefetchLoader, blockerState, routeItemData, restoreScroll, currentLoaderFallback, isLoading, loaderState, invalidate }) => {
46
+ var Provider = ({ children, updateLocation, prefetchLoader, invalidate }) => {
49
47
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ActionsContext.Provider, {
50
48
  value: {
51
49
  updateLocation,
52
- updateBlockedRoute,
53
50
  prefetchLoader,
54
- setContext,
55
- restoreScroll,
56
51
  invalidate
57
52
  },
58
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DataContext.Provider, {
59
- value: { context },
60
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(NavigationContext.Provider, {
61
- value: {
62
- blockerState,
63
- routeItemData,
64
- currentLoaderFallback,
65
- isLoading,
66
- loaderState
67
- },
68
- children
69
- })
70
- })
53
+ children
71
54
  });
72
55
  };
73
56
  //#endregion
@@ -80,10 +63,56 @@ var useLatest = (value) => {
80
63
  return ref;
81
64
  };
82
65
  //#endregion
66
+ //#region state/createState.ts
67
+ var create = (initialState) => {
68
+ let state = initialState;
69
+ const subscribers = /* @__PURE__ */ new Set();
70
+ const getState = () => state;
71
+ const setState = (action) => {
72
+ const prevState = state;
73
+ const nextState = typeof action === "function" ? action(state) : action;
74
+ if (!Object.is(state, nextState)) {
75
+ state = nextState;
76
+ subscribers.forEach((listener) => listener(state, prevState));
77
+ }
78
+ };
79
+ const subscribe = (listener) => {
80
+ subscribers.add(listener);
81
+ return () => subscribers.delete(listener);
82
+ };
83
+ return {
84
+ subscribe,
85
+ getState,
86
+ setState
87
+ };
88
+ };
89
+ var useGlobalState = ({ subscribe, getState, setState }) => {
90
+ return [useSyncExternalStore(subscribe, getState), setState];
91
+ };
92
+ var createState = (initialState) => {
93
+ const store = create(initialState);
94
+ return () => useGlobalState(store);
95
+ };
96
+ //#endregion
83
97
  //#region constants.ts
84
98
  var emptyLoaderState = {};
85
99
  //#endregion
86
- //#region \0@oxc-project+runtime@0.133.0/helpers/esm/typeof.js
100
+ //#region state/state.ts
101
+ var useIsLoading = createState(false);
102
+ var useBlockedRoute = createState({
103
+ from: "",
104
+ to: ""
105
+ });
106
+ var useLoaderFallback = createState(void 0);
107
+ var useRouteItemData = createState({
108
+ routeItem: void 0,
109
+ location: {}
110
+ });
111
+ var useCurrentLoaderState = createState(emptyLoaderState);
112
+ var useScrollMap = createState({});
113
+ var useContextState = createState({});
114
+ //#endregion
115
+ //#region \0@oxc-project+runtime@0.132.0/helpers/typeof.js
87
116
  function _typeof(o) {
88
117
  "@babel/helpers - typeof";
89
118
  return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function(o) {
@@ -93,7 +122,7 @@ function _typeof(o) {
93
122
  }, _typeof(o);
94
123
  }
95
124
  //#endregion
96
- //#region \0@oxc-project+runtime@0.133.0/helpers/esm/toPrimitive.js
125
+ //#region \0@oxc-project+runtime@0.132.0/helpers/toPrimitive.js
97
126
  function toPrimitive(t, r) {
98
127
  if ("object" != _typeof(t) || !t) return t;
99
128
  var e = t[Symbol.toPrimitive];
@@ -105,13 +134,13 @@ function toPrimitive(t, r) {
105
134
  return ("string" === r ? String : Number)(t);
106
135
  }
107
136
  //#endregion
108
- //#region \0@oxc-project+runtime@0.133.0/helpers/esm/toPropertyKey.js
137
+ //#region \0@oxc-project+runtime@0.132.0/helpers/toPropertyKey.js
109
138
  function toPropertyKey(t) {
110
139
  var i = toPrimitive(t, "string");
111
140
  return "symbol" == _typeof(i) ? i : i + "";
112
141
  }
113
142
  //#endregion
114
- //#region \0@oxc-project+runtime@0.133.0/helpers/esm/defineProperty.js
143
+ //#region \0@oxc-project+runtime@0.132.0/helpers/defineProperty.js
115
144
  function _defineProperty(e, r, t) {
116
145
  return (r = toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
117
146
  value: t,
@@ -186,34 +215,19 @@ var comparePaths = (el, pathname) => {
186
215
  return splitElementPath.every((item, index) => item === splitPathname[index + (index ? 1 : 0)]) && splitPathname.length === splitElementPath.length + paramsLength;
187
216
  };
188
217
  //#endregion
189
- //#region hooks/useHandleNavigation.ts
218
+ //#region hooks/useNavigation.ts
190
219
  var ALL_LOCATIONS = "*";
191
- var useHandleNavigation = ({ routes, context, revalidateCache, setContext, isCacheItemFresh, loaderStateRef, clearTimestamp }) => {
220
+ var useNavigation = ({ routes, revalidateCache, isCacheItemFresh, loaderStateRef }) => {
221
+ const [, setIsLoading] = useIsLoading();
222
+ const [, setScrollMap] = useScrollMap();
223
+ const [, setRouteItemData] = useRouteItemData();
224
+ const [, setLoaderState] = useCurrentLoaderState();
225
+ const [, setCurrentLoaderFallback] = useLoaderFallback();
226
+ const [context, setContext] = useContextState();
227
+ const [blockedRoute, setBlockedRoute] = useBlockedRoute();
192
228
  const { isAnimated, showFallbackOnAnimation: showFallback } = routerConfig;
193
- const [isLoading, setIsLoading] = useState(false);
194
- const [blockedRoute, setBlockedRoute] = useState({
195
- from: "",
196
- to: ""
197
- });
198
- const [routeItemData, setRouteItemData] = useState({
199
- routeItem: void 0,
200
- location: {}
201
- });
202
- const [scrollMap, setScrollMap] = useState({});
203
- const [currentLoaderFallback, setCurrentLoaderFallback] = useState();
204
- const [loaderState, setLoaderState] = useState(emptyLoaderState);
205
229
  const prevPathname = useRef("");
206
230
  const navigationSeq = useRef(0);
207
- const scrollMapLatest = useLatest(scrollMap);
208
- const restoreScroll = useCallback(() => {
209
- if (!prevPathname.current || !scrollMapLatest.current[prevPathname.current]) return;
210
- requestAnimationFrame(() => {
211
- window.scrollTo({
212
- top: scrollMapLatest.current[prevPathname.current],
213
- behavior: "smooth"
214
- });
215
- });
216
- }, [scrollMapLatest]);
217
231
  const navigation = useCallback((nextLocation, routeItem) => {
218
232
  setRouteItemData({
219
233
  routeItem,
@@ -226,7 +240,13 @@ var useHandleNavigation = ({ routes, context, revalidateCache, setContext, isCac
226
240
  const fullPath = nextLocation.search ? `${nextLocation.pathname}${nextLocation.search}` : nextLocation.pathname;
227
241
  if (fullPath === window.location.pathname + window.location.search) return;
228
242
  history.pushState(null, "", fullPath);
229
- }, [loaderStateRef]);
243
+ }, [
244
+ loaderStateRef,
245
+ setCurrentLoaderFallback,
246
+ setIsLoading,
247
+ setLoaderState,
248
+ setRouteItemData
249
+ ]);
230
250
  const transitionedNavigation = useCallback((nextLocation, routeItem) => {
231
251
  if (!isAnimated) {
232
252
  navigation(nextLocation, routeItem);
@@ -238,45 +258,6 @@ var useHandleNavigation = ({ routes, context, revalidateCache, setContext, isCac
238
258
  navigation(nextLocation, routeItem);
239
259
  }
240
260
  }, [navigation, isAnimated]);
241
- const invalidate = useCallback(async (pathname = routeItemData.location.pathname) => {
242
- if (typeof pathname !== "string") return;
243
- const routeItem = routes.find((el) => comparePaths(el, pathname));
244
- const resultParams = getParamsObject({
245
- params: routeItem?.params,
246
- pathname
247
- });
248
- clearTimestamp(pathname);
249
- try {
250
- if (routeItem?.beforeLoad) await routeItem.beforeLoad({
251
- context,
252
- redirect: () => Promise.resolve(),
253
- params: resultParams,
254
- setContext
255
- });
256
- loaderStateRef.current = {
257
- ...loaderStateRef.current,
258
- beforeLoadError: null
259
- };
260
- } catch (error) {
261
- loaderStateRef.current = {
262
- ...loaderStateRef.current,
263
- beforeLoadError: error
264
- };
265
- }
266
- await revalidateCache({
267
- routeItem,
268
- pathname
269
- });
270
- if (pathname === routeItemData.location.pathname) setLoaderState(loaderStateRef.current);
271
- }, [
272
- clearTimestamp,
273
- context,
274
- loaderStateRef,
275
- revalidateCache,
276
- routeItemData.location.pathname,
277
- routes,
278
- setContext
279
- ]);
280
261
  const navigationHandler = useCallback(async (nextLocation) => {
281
262
  navigationSeq.current = navigationSeq.current + 1;
282
263
  const seq = navigationSeq.current;
@@ -336,40 +317,20 @@ var useHandleNavigation = ({ routes, context, revalidateCache, setContext, isCac
336
317
  });
337
318
  }, [
338
319
  context,
320
+ isAnimated,
321
+ isCacheItemFresh,
322
+ loaderStateRef,
339
323
  revalidateCache,
340
324
  routes,
341
- transitionedNavigation,
342
325
  setContext,
343
- isCacheItemFresh,
344
- isAnimated,
326
+ setCurrentLoaderFallback,
327
+ setIsLoading,
328
+ setScrollMap,
345
329
  showFallback,
346
- loaderStateRef
330
+ transitionedNavigation
347
331
  ]);
348
332
  const setNextLocationRef = useLatest(navigationHandler);
349
- const updateBlockedRoute = useCallback(({ type, payload = "" }) => setBlockedRoute((prevState) => {
350
- if (prevState.from === payload && type === "charge") return prevState;
351
- if (payload && prevState.from !== payload && type === "charge") return {
352
- ...prevState,
353
- from: payload
354
- };
355
- if (type === "reset") return {
356
- ...prevState,
357
- to: ""
358
- };
359
- if (type === "process") setNextLocationRef.current({ pathname: prevState.to });
360
- if (!prevState.from && !prevState.to) return prevState;
361
- return {
362
- from: "",
363
- to: ""
364
- };
365
- }), [setNextLocationRef]);
366
- const updateLocation = useCallback(async (nextLocation) => {
367
- if (blockedRoute.from) setBlockedRoute((prevState) => ({
368
- ...prevState,
369
- to: nextLocation.pathname
370
- }));
371
- else await setNextLocationRef.current(nextLocation);
372
- }, [blockedRoute.from, setNextLocationRef]);
333
+ const updateLocation = useCallback(async (nextLocation) => await setNextLocationRef.current(nextLocation), [setNextLocationRef]);
373
334
  useEffect(() => {
374
335
  const handler = async (event) => {
375
336
  const newLocation = parseWindowLocation(event.target.location);
@@ -383,34 +344,28 @@ var useHandleNavigation = ({ routes, context, revalidateCache, setContext, isCac
383
344
  };
384
345
  window.addEventListener("popstate", handler);
385
346
  return () => window.removeEventListener("popstate", handler);
386
- }, [blockedRoute.from, setNextLocationRef]);
347
+ }, [
348
+ blockedRoute.from,
349
+ setBlockedRoute,
350
+ setNextLocationRef
351
+ ]);
387
352
  useEffect(() => {
388
353
  const currentLocation = parseWindowLocation(window.location);
389
354
  setNextLocationRef.current(currentLocation);
390
355
  prevPathname.current = currentLocation.pathname;
391
356
  }, [setNextLocationRef]);
392
- return {
393
- blockerState: useMemo(() => {
394
- if (blockedRoute.from && blockedRoute.to) return "blocked";
395
- if (blockedRoute.from) return "charged";
396
- return "unblocked";
397
- }, [blockedRoute]),
398
- updateLocation,
399
- updateBlockedRoute,
400
- routeItemData,
401
- restoreScroll,
402
- currentLoaderFallback,
403
- isLoading,
404
- loaderState,
405
- invalidate
406
- };
357
+ return updateLocation;
407
358
  };
408
359
  //#endregion
409
360
  //#region hooks/useLoader.ts
410
- var useLoader = ({ routes, context, setContext }) => {
361
+ var useLoader = (routes) => {
362
+ const [, setLoaderState] = useCurrentLoaderState();
363
+ const [routeItemData] = useRouteItemData();
364
+ const [context, setContext] = useContextState();
365
+ const loaderStateRef = useRef(emptyLoaderState);
366
+ const latestPathname = useLatest(routeItemData.location.pathname);
411
367
  const timestampMapRef = useRef(/* @__PURE__ */ new Map());
412
368
  const loaderMapRef = useRef({});
413
- const loaderStateRef = useRef(emptyLoaderState);
414
369
  const loadingPromises = useRef(/* @__PURE__ */ new Map());
415
370
  const isCacheItemFresh = useCallback(({ routeItem, pathname }) => {
416
371
  if (!routeItem) return true;
@@ -476,70 +431,64 @@ var useLoader = ({ routes, context, setContext }) => {
476
431
  revalidateCache,
477
432
  isCacheItemFresh,
478
433
  loaderStateRef,
479
- clearTimestamp: useCallback((pathname) => {
434
+ invalidate: useCallback(async (pathname = latestPathname.current) => {
435
+ if (typeof pathname !== "string") return;
436
+ const routeItem = routes.find((el) => comparePaths(el, pathname));
437
+ const resultParams = getParamsObject({
438
+ params: routeItem?.params,
439
+ pathname
440
+ });
480
441
  timestampMapRef.current.delete(pathname);
481
- }, [])
442
+ try {
443
+ if (routeItem?.beforeLoad) await routeItem.beforeLoad({
444
+ context,
445
+ redirect: () => Promise.resolve(),
446
+ params: resultParams,
447
+ setContext
448
+ });
449
+ loaderStateRef.current = {
450
+ ...loaderStateRef.current,
451
+ beforeLoadError: null
452
+ };
453
+ } catch (error) {
454
+ loaderStateRef.current = {
455
+ ...loaderStateRef.current,
456
+ beforeLoadError: error
457
+ };
458
+ }
459
+ await revalidateCache({
460
+ routeItem,
461
+ pathname
462
+ });
463
+ if (pathname === latestPathname.current) setLoaderState(loaderStateRef.current);
464
+ }, [
465
+ context,
466
+ loaderStateRef,
467
+ revalidateCache,
468
+ latestPathname,
469
+ routes,
470
+ setContext,
471
+ setLoaderState
472
+ ])
482
473
  };
483
474
  };
484
475
  //#endregion
485
476
  //#region components/RouterProvider.tsx
486
- var RouterProvider = ({ children, routes, context: initialContext = {} }) => {
487
- const [context, setContext] = useState(initialContext);
488
- const { prefetchLoader, revalidateCache, isCacheItemFresh, loaderStateRef, clearTimestamp } = useLoader({
489
- routes,
490
- context,
491
- setContext
492
- });
493
- const { blockerState, updateLocation, updateBlockedRoute, routeItemData, restoreScroll, currentLoaderFallback, isLoading, loaderState, invalidate } = useHandleNavigation({
494
- routes,
495
- context,
496
- setContext,
497
- revalidateCache,
498
- isCacheItemFresh,
499
- loaderStateRef,
500
- clearTimestamp
501
- });
477
+ var RouterProvider = ({ children, routes }) => {
478
+ const { prefetchLoader, revalidateCache, isCacheItemFresh, loaderStateRef, invalidate } = useLoader(routes);
502
479
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Provider, {
503
- ...useMemo(() => ({
504
- updateLocation,
505
- prefetchLoader,
506
- updateBlockedRoute,
507
- blockerState,
508
- context,
509
- setContext,
510
- routeItemData,
511
- restoreScroll,
512
- currentLoaderFallback,
513
- isLoading,
514
- loaderState,
515
- invalidate
516
- }), [
517
- blockerState,
518
- context,
519
- prefetchLoader,
520
- routeItemData,
521
- updateBlockedRoute,
522
- updateLocation,
523
- currentLoaderFallback,
524
- restoreScroll,
525
- isLoading,
526
- loaderState,
527
- invalidate
528
- ]),
480
+ updateLocation: useNavigation({
481
+ routes,
482
+ revalidateCache,
483
+ isCacheItemFresh,
484
+ loaderStateRef
485
+ }),
486
+ invalidate,
487
+ prefetchLoader,
529
488
  children
530
489
  });
531
490
  };
532
491
  //#endregion
533
- //#region hooks/useServiceContext.ts
534
- var useServiceState = (reactContext) => {
535
- const context = useContext(reactContext);
536
- if (!Object.keys(context).length) throw new Error("hooks and Router component must be used within RouterProvider");
537
- return context;
538
- };
539
- var useNavigationState = () => useServiceState(NavigationContext);
540
- var useRouterActions = () => useServiceState(ActionsContext);
541
- var useRouterData = () => useServiceState(DataContext);
542
- //#endregion
543
492
  //#region hooks/useApplyCustomAnimation.ts
544
493
  var useApplyCustomAnimation = (animationDuration) => {
545
494
  useEffect(() => {
@@ -579,8 +528,18 @@ var useApplyCustomAnimation = (animationDuration) => {
579
528
  //#endregion
580
529
  //#region hooks/usePreserveScroll.ts
581
530
  var usePreserveScroll = (preserveScroll) => {
582
- const { restoreScroll } = useRouterActions();
583
- const { routeItemData: { location: { pathname } } } = useNavigationState();
531
+ const [routeItemData] = useRouteItemData();
532
+ const [scrollMap] = useScrollMap();
533
+ const { pathname } = routeItemData.location;
534
+ const restoreScroll = useCallback(() => {
535
+ if (!pathname || !scrollMap[pathname]) return;
536
+ requestAnimationFrame(() => {
537
+ window.scrollTo({
538
+ top: scrollMap[pathname],
539
+ behavior: "smooth"
540
+ });
541
+ });
542
+ }, [pathname, scrollMap]);
584
543
  useEffect(() => {
585
544
  if (preserveScroll) restoreScroll();
586
545
  }, [
@@ -595,6 +554,14 @@ var useSetRouterConfig = (routerProps) => {
595
554
  useEffect(() => routerConfig.configure(routerProps), [routerProps]);
596
555
  };
597
556
  //#endregion
557
+ //#region hooks/useSetInitialContext.ts
558
+ var useSetInitialContext = (initialContext) => {
559
+ const [, setContext] = useContextState();
560
+ useEffect(() => {
561
+ if (initialContext) setContext(initialContext);
562
+ }, [initialContext, setContext]);
563
+ };
564
+ //#endregion
598
565
  //#region components/Spinner.tsx
599
566
  var Spinner = () => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "cr-spinner" });
600
567
  //#endregion
@@ -606,8 +573,11 @@ var renderElement = (Component) => {
606
573
  //#endregion
607
574
  //#region components/Router.tsx
608
575
  var EmptyBoundary = ({ children }) => children;
609
- var Router = ({ isAnimated, animationDuration, spinner = true, preserveScroll = true, showFallbackOnAnimation = false, prefetch = "hover", hoverPrefetchDelay = 150, errorBoundary: ErrorBoundary = EmptyBoundary }) => {
610
- const { routeItemData: { routeItem, location }, loaderState, currentLoaderFallback, isLoading } = useNavigationState();
576
+ var Router = ({ isAnimated, animationDuration, spinner = true, preserveScroll = true, showFallbackOnAnimation = false, prefetch = "hover", hoverPrefetchDelay = 150, errorBoundary: ErrorBoundary = EmptyBoundary, context: initialContext }) => {
577
+ const [isLoading] = useIsLoading();
578
+ const [currentLoaderFallback] = useLoaderFallback();
579
+ const [routeItemData] = useRouteItemData();
580
+ const [loaderState] = useCurrentLoaderState();
611
581
  usePreserveScroll(preserveScroll);
612
582
  useApplyCustomAnimation(animationDuration);
613
583
  useSetRouterConfig({
@@ -616,9 +586,11 @@ var Router = ({ isAnimated, animationDuration, spinner = true, preserveScroll =
616
586
  prefetch,
617
587
  hoverPrefetchDelay
618
588
  });
589
+ useSetInitialContext(initialContext);
619
590
  const showErrorElement = !isLoading && Boolean(loaderState.loaderError || loaderState.beforeLoadError);
620
591
  const showSpinner = spinner && isAnimated && isLoading;
621
592
  const loadingContent = !showErrorElement && isLoading;
593
+ const { routeItem, location } = routeItemData;
622
594
  if ((showFallbackOnAnimation || !isAnimated) && loadingContent) return renderElement(currentLoaderFallback);
623
595
  if (!showFallbackOnAnimation && isAnimated && loadingContent) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, {});
624
596
  if (!routeItem) return null;
@@ -629,22 +601,45 @@ var Router = ({ isAnimated, animationDuration, spinner = true, preserveScroll =
629
601
  });
630
602
  };
631
603
  //#endregion
604
+ //#region hooks/useServiceContext.ts
605
+ var useServiceState = (reactContext) => {
606
+ const context = useContext(reactContext);
607
+ if (!Object.keys(context).length) throw new Error("hooks and Router component must be used within RouterProvider");
608
+ return context;
609
+ };
610
+ var useRouterActions = () => useServiceState(ActionsContext);
611
+ //#endregion
632
612
  //#region hooks/useLocation.ts
633
613
  var useLocation = () => {
634
- const { routeItemData: { location } } = useNavigationState();
635
- return location;
614
+ const [routeItemData] = useRouteItemData();
615
+ return routeItemData.location;
636
616
  };
637
617
  //#endregion
638
618
  //#region hooks/useNavigate.ts
639
619
  var useNavigate = () => {
620
+ const [blockedRoute, setBlockedRoute] = useBlockedRoute();
640
621
  const { updateLocation } = useRouterActions();
641
622
  const locationRef = useLatest(useLocation());
623
+ const blockedRouteRef = useLatest(blockedRoute);
642
624
  return useCallback(async (arg) => {
643
- if (typeof arg === "number") return history.go(arg);
625
+ if (arg !== -1 && blockedRouteRef.current.from) {
626
+ const to = typeof arg === "object" ? arg.pathname : arg;
627
+ setBlockedRoute((prevState) => ({
628
+ ...prevState,
629
+ to
630
+ }));
631
+ return;
632
+ }
633
+ if (arg === -1) return history.go(arg);
644
634
  if (typeof arg === "string") {
645
635
  if (arg !== locationRef.current.pathname) await updateLocation({ pathname: arg });
646
636
  } else if (JSON.stringify(arg) !== JSON.stringify(locationRef.current)) await updateLocation(arg);
647
- }, [updateLocation, locationRef]);
637
+ }, [
638
+ blockedRouteRef,
639
+ locationRef,
640
+ setBlockedRoute,
641
+ updateLocation
642
+ ]);
648
643
  };
649
644
  //#endregion
650
645
  //#region components/Link.tsx
@@ -711,7 +706,8 @@ var Link = ({ children, to, prefetch: prefetchLink, hoverPrefetchDelay }) => {
711
706
  //#endregion
712
707
  //#region hooks/useParams.ts
713
708
  var useParams = () => {
714
- const { routeItemData: { routeItem, location: { pathname } } } = useNavigationState();
709
+ const [routeItemData] = useRouteItemData();
710
+ const { routeItem, location: { pathname } } = routeItemData;
715
711
  if (!routeItem) return void 0;
716
712
  return getParamsObject({
717
713
  params: routeItem?.params,
@@ -721,7 +717,7 @@ var useParams = () => {
721
717
  //#endregion
722
718
  //#region hooks/useLoaderState.ts
723
719
  var useLoaderState = () => {
724
- const { loaderState } = useNavigationState();
720
+ const [loaderState] = useCurrentLoaderState();
725
721
  return loaderState;
726
722
  };
727
723
  //#endregion
@@ -733,9 +729,32 @@ var useInvalidate = () => {
733
729
  //#endregion
734
730
  //#region hooks/useBlocker.ts
735
731
  var useBlocker = (blockerFn) => {
736
- const { blockerState } = useNavigationState();
737
- const { routeItemData: { location: { pathname } } } = useNavigationState();
738
- const { updateBlockedRoute } = useRouterActions();
732
+ const [blockedRoute, setBlockedRoute] = useBlockedRoute();
733
+ const { updateLocation } = useRouterActions();
734
+ const [routeItemData] = useRouteItemData();
735
+ const { location: { pathname } } = routeItemData;
736
+ const updateBlockedRoute = useCallback(({ type, payload = "" }) => setBlockedRoute((prevState) => {
737
+ if (prevState.from === payload && type === "charge") return prevState;
738
+ if (payload && prevState.from !== payload && type === "charge") return {
739
+ ...prevState,
740
+ from: payload
741
+ };
742
+ if (type === "reset") return {
743
+ ...prevState,
744
+ to: ""
745
+ };
746
+ if (type === "process") updateLocation({ pathname: prevState.to });
747
+ if (!prevState.from && !prevState.to) return prevState;
748
+ return {
749
+ from: "",
750
+ to: ""
751
+ };
752
+ }), [setBlockedRoute, updateLocation]);
753
+ const blockerState = useMemo(() => {
754
+ if (blockedRoute.from && blockedRoute.to) return "blocked";
755
+ if (blockedRoute.from) return "charged";
756
+ return "unblocked";
757
+ }, [blockedRoute]);
739
758
  const shouldBlock = blockerFn();
740
759
  useEffect(() => updateBlockedRoute(shouldBlock ? {
741
760
  type: "charge",
@@ -767,8 +786,7 @@ var useBeforeUnload = (callback) => {
767
786
  //#endregion
768
787
  //#region hooks/useRouterContext.ts
769
788
  var useRouterContext = () => {
770
- const { context } = useRouterData();
771
- const { setContext } = useRouterActions();
789
+ const [context, setContext] = useContextState();
772
790
  return {
773
791
  context,
774
792
  setContext
@@ -874,6 +892,12 @@ var useHistoricalTrail = () => {
874
892
  return trail;
875
893
  };
876
894
  //#endregion
895
+ //#region context/FormContext.ts
896
+ var FormContext = createContext({ isSubmitting: false });
897
+ //#endregion
898
+ //#region hooks/useFormContext.ts
899
+ var useFormContext = () => useContext(FormContext);
900
+ //#endregion
877
901
  //#region utils/adapter.ts
878
902
  var adapter = {
879
903
  string: { parse: (params) => params[0] || "" },
@@ -936,4 +960,4 @@ var adapter = {
936
960
  })
937
961
  };
938
962
  //#endregion
939
- export { Link, Router, RouterProvider, adapter, createRouter, useBeforeUnload, useBlocker, useHistoricalTrail, useInvalidate, useLoaderState, useLocation, useNavigate, useParams, useQueryParam, useRouterContext, useSearchParams };
963
+ export { Link, Router, RouterProvider, adapter, createRouter, useBeforeUnload, useBlocker, useFormContext, useHistoricalTrail, useInvalidate, useLoaderState, useLocation, useNavigate, useParams, useQueryParam, useRouterContext, useSearchParams };
@@ -0,0 +1,3 @@
1
+ import { PropsWithChildren } from 'react';
2
+ import { FormContextProps } from '../context/FormContext';
3
+ export declare const FormProvider: ({ children, isSubmitting }: PropsWithChildren<FormContextProps>) => import("react/jsx-runtime").JSX.Element;
@@ -1,7 +1,7 @@
1
1
  import { type ReactNode } from 'react';
2
- import { type ActionsContextValue, type DataContextValue, type NavigationContextValue } from '../context/RouterProviderContext';
3
- type ProviderProps = NavigationContextValue & ActionsContextValue & DataContextValue & {
2
+ import { type ActionsContextValue } from '../context/RouterProviderContext';
3
+ type ProviderProps = ActionsContextValue & {
4
4
  children: ReactNode;
5
5
  };
6
- export declare const Provider: ({ children, setContext, context, updateBlockedRoute, updateLocation, prefetchLoader, blockerState, routeItemData, restoreScroll, currentLoaderFallback, isLoading, loaderState, invalidate, }: ProviderProps) => import("react/jsx-runtime").JSX.Element;
6
+ export declare const Provider: ({ children, updateLocation, prefetchLoader, invalidate }: ProviderProps) => import("react/jsx-runtime").JSX.Element;
7
7
  export {};
@@ -0,0 +1,3 @@
1
+ type SetStateAction<T> = ((prevState: T) => T) | T;
2
+ export declare const createState: <T>(initialState: T) => () => readonly [T, (action: SetStateAction<T>) => void];
3
+ export {};
@@ -0,0 +1,20 @@
1
+ import { LoaderState, RouteItemData } from '../types/global';
2
+ export declare const useIsLoading: () => readonly [boolean, (action: boolean | ((prevState: boolean) => boolean)) => void];
3
+ export declare const useBlockedRoute: () => readonly [{
4
+ from: string;
5
+ to: string;
6
+ }, (action: {
7
+ from: string;
8
+ to: string;
9
+ } | ((prevState: {
10
+ from: string;
11
+ to: string;
12
+ }) => {
13
+ from: string;
14
+ to: string;
15
+ })) => void];
16
+ export declare const useLoaderFallback: () => readonly [(import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | (() => import("react").ReactElement)) | undefined, (action: (import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | (() => import("react").ReactElement)) | ((prevState: (import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | (() => import("react").ReactElement)) | undefined) => (import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | (() => import("react").ReactElement)) | undefined) | undefined) => void];
17
+ export declare const useRouteItemData: () => readonly [RouteItemData, (action: RouteItemData | ((prevState: RouteItemData) => RouteItemData)) => void];
18
+ export declare const useCurrentLoaderState: () => readonly [LoaderState, (action: LoaderState | ((prevState: LoaderState) => LoaderState)) => void];
19
+ export declare const useScrollMap: () => readonly [Record<string, number>, (action: Record<string, number> | ((prevState: Record<string, number>) => Record<string, number>)) => void];
20
+ export declare const useContextState: () => readonly [Record<string, unknown>, (action: Record<string, unknown> | ((prevState: Record<string, unknown>) => Record<string, unknown>)) => void];
@@ -27,6 +27,12 @@ export type ClientRouteItem = {
27
27
  params: Record<string, string>;
28
28
  setContext: Dispatch<SetStateAction<Record<string, unknown>>>;
29
29
  }) => Promise<void>;
30
+ actions?: (arg: {
31
+ context: Record<string, unknown>;
32
+ params: Record<string, string>;
33
+ invalidate: (path?: string) => Promise<void>;
34
+ setContext: Dispatch<SetStateAction<Record<string, unknown>>>;
35
+ }) => Record<string, (arg: FormData) => Promise<unknown> | Promise<void> | void | unknown>;
30
36
  };
31
37
  export type RouteItem = ClientRouteItem & {
32
38
  element: Element;
@@ -42,10 +48,6 @@ export type Location = {
42
48
  state?: unknown;
43
49
  };
44
50
  export type BlockerState = 'blocked' | 'unblocked' | 'charged';
45
- export type UpdateBlockedRouteProps = {
46
- type: 'process' | 'reset' | 'charge' | 'unblock';
47
- payload?: string;
48
- };
49
51
  export type RevalidateCacheArgs = {
50
52
  pathname: string;
51
53
  routeItem?: RouteItem;
@@ -74,5 +76,6 @@ export type RouterProps = {
74
76
  errorBoundary?: ComponentType<{
75
77
  children: ReactNode;
76
78
  }>;
79
+ context?: Record<string, unknown>;
77
80
  };
78
81
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clear-react-router",
3
- "version": "1.6.5",
3
+ "version": "1.6.7",
4
4
  "description": "A lightweight, type-safe routing library for React applications",
5
5
  "author": "Andrew Bubnov",
6
6
  "scripts": {
@@ -1,26 +0,0 @@
1
- import { type Dispatch, RefObject, type SetStateAction } from 'react';
2
- import { BlockerState, LoaderState, Location, RevalidateCacheArgs, RouteItem, RouteItemData, UpdateBlockedRouteProps } from '../types/global';
3
- type UseHandleNavigation = {
4
- routes: RouteItem[];
5
- context: Record<string, unknown>;
6
- revalidateCache(arg: RevalidateCacheArgs): Promise<unknown>;
7
- setContext: Dispatch<SetStateAction<Record<string, unknown>>>;
8
- isCacheItemFresh(arg: {
9
- routeItem?: RouteItem;
10
- pathname: string;
11
- }): boolean;
12
- loaderStateRef: RefObject<LoaderState>;
13
- clearTimestamp(path: string): void;
14
- };
15
- export declare const useHandleNavigation: ({ routes, context, revalidateCache, setContext, isCacheItemFresh, loaderStateRef, clearTimestamp, }: UseHandleNavigation) => {
16
- blockerState: BlockerState;
17
- updateLocation: (nextLocation: Location) => Promise<void>;
18
- updateBlockedRoute: ({ type, payload }: UpdateBlockedRouteProps) => void;
19
- routeItemData: RouteItemData;
20
- restoreScroll: () => void;
21
- currentLoaderFallback: (import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | (() => import("react").ReactElement)) | undefined;
22
- isLoading: boolean;
23
- loaderState: LoaderState;
24
- invalidate: (pathname?: string) => Promise<void>;
25
- };
26
- export {};