clear-react-router 1.6.9 → 1.7.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/README.md CHANGED
@@ -272,7 +272,7 @@ Actions can be executed declaratively with `<Form />` or imperatively with `useA
272
272
  import { Form, useFormContext } from '../clear-router';
273
273
 
274
274
  const SubmitButton = () => {
275
- const { isSubmitting } = useFormContext()
275
+ const {isSubmitting} = useFormContext()
276
276
  return <button disabled={isSubmitting} type='submit'>Save</button>
277
277
  }
278
278
 
@@ -305,6 +305,13 @@ After a successful action:
305
305
 
306
306
  `useAction` provides direct access to a route action without rendering a `<Form />`.
307
307
 
308
+ ### Arguments
309
+
310
+ | Argument | Type | Description |
311
+ |----------|------|-------------|
312
+ | `action` | `string` | Name of the route action to execute. Must match a key returned from the route's `actions` configuration. |
313
+ | `options` | `{ onSuccess?, onError? }` | Optional callbacks invoked after the action succeeds or fails. |
314
+
308
315
  ```tsx
309
316
  const save = useAction('save');
310
317
 
@@ -508,29 +515,6 @@ useEffect(() => {
508
515
  }, [state, process, reset]);
509
516
  ```
510
517
 
511
- ### `useBeforeUnload(callback?)`
512
-
513
- Executes a callback when the page is about to be closed or reloaded. Perfect for auto-saving data at the last moment.
514
-
515
- **Parameters:**
516
-
517
- | Parameter | Type | Description |
518
- |-----------|------|-------------|
519
- | `callback` | `() => void \| undefined` | Function to execute before page unload (e.g., auto-save) |
520
-
521
- **Note:** This hook does not show a browser confirmation dialog. It silently executes the callback, allowing you to save user data in the background before the page closes.
522
-
523
- ```tsx
524
- const [text, setText] = useState('');
525
- const onSave = useCallback(() => {
526
- localStorage.setItem('draft', text);
527
- }, [text]);
528
-
529
- // Auto-save when user tries to close/reload the page
530
- useBeforeUnload(text ? onSave : undefined);
531
- ```
532
- > **Note:** Pass `undefined` to disable the handler (e.g., if there is no changes).
533
-
534
518
  ### `useQueryParam()`
535
519
 
536
520
  A flexible hook for working with typed query parameters. You provide an adapter object with `parse` and `serialize` functions, and it returns the parsed value and a setter.
@@ -701,18 +685,6 @@ function ProductFilter() {
701
685
 
702
686
  > **Note:** `getSearchParams` returns `string` for single values, `string[]` for multiple values, and `''` if the key is not found.
703
687
 
704
- ### `useHistoricalTrail()`
705
-
706
- Returns an array of pathnames representing the user's actual navigation history. Perfect for **history-based breadcrumbs** in dashboards, admin panels, multi-step forms, or any app where users navigate non-linearly.
707
-
708
- **Returns:** Array of pathnames in chronological visit order (e.g., `['/dashboard', '/users', '/settings']`)
709
-
710
- **Key features:**
711
- - **Chronological order** — Paths are stored in the order the user visited them
712
- - **Unique entries** — Revisiting a page trims the trail to that point
713
- - **Respects navigation blocking** — Only successful navigations are added
714
- - **Redirect-safe** — Redirected pages are not added to the trail
715
-
716
688
  ## Lazy Loading
717
689
 
718
690
  Clear Router supports code-splitting out of the box. Simply pass a function that returns a dynamic import:
@@ -1 +1,6 @@
1
- export declare const useAction: (actionKey: string, onError?: (args: unknown) => void) => (formData: FormData) => Promise<void>;
1
+ type Options = Partial<{
2
+ onSuccess: (args: unknown) => void;
3
+ onError: (args: unknown) => void;
4
+ }> | undefined;
5
+ export declare const useAction: (action: string, options?: Options) => (formData: FormData) => Promise<void>;
6
+ export {};
@@ -0,0 +1,7 @@
1
+ export declare const useActionParams: () => {
2
+ invalidate: (path?: string) => Promise<void>;
3
+ routeItem: import("..").RouteItem | undefined;
4
+ latestContext: import("react").RefObject<Record<string, unknown>>;
5
+ params: Record<string, string>;
6
+ setContext: (action: Record<string, unknown> | ((prevState: Record<string, unknown>) => Record<string, unknown>)) => void;
7
+ };
package/dist/index.d.ts CHANGED
@@ -9,11 +9,9 @@ export { useLoaderState } from './hooks/useLoaderState';
9
9
  export { useInvalidate } from './hooks/useInvalidate';
10
10
  export { useBlocker } from './hooks/useBlocker';
11
11
  export { useAction } from './hooks/useAction';
12
- export { useBeforeUnload } from './hooks/useBeforeUnload';
13
12
  export { useRouterContext } from './hooks/useRouterContext';
14
13
  export { useQueryParam } from './hooks/useQueryParam';
15
14
  export { useSearchParams } from './hooks/useSearchParams';
16
- export { useHistoricalTrail } from './hooks/useHistoricalTrail';
17
15
  export { useFormContext } from './hooks/useFormContext';
18
16
  export { adapter } from './utils/adapter';
19
17
  export { createRouter } from './utils/utils';
package/dist/index.js CHANGED
@@ -704,6 +704,15 @@ var Link = ({ children, to, prefetch: prefetchLink, hoverPrefetchDelay }) => {
704
704
  });
705
705
  };
706
706
  //#endregion
707
+ //#region context/FormContext.ts
708
+ var FormContext = createContext({ isSubmitting: false });
709
+ //#endregion
710
+ //#region provider/FormProvider.tsx
711
+ var FormProvider = ({ children, isSubmitting }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FormContext.Provider, {
712
+ value: { isSubmitting },
713
+ children
714
+ });
715
+ //#endregion
707
716
  //#region hooks/useInvalidate.ts
708
717
  var useInvalidate = () => {
709
718
  const { invalidate } = useRouterActions();
@@ -714,36 +723,38 @@ var useInvalidate = () => {
714
723
  var useParams = () => {
715
724
  const [routeItemData] = useRouteItemData();
716
725
  const { routeItem, location: { pathname } } = routeItemData;
717
- if (!routeItem) return void 0;
718
- return getParamsObject({
726
+ return useMemo(() => routeItem ? getParamsObject({
719
727
  params: routeItem?.params,
720
728
  pathname
721
- });
729
+ }) : void 0, [pathname, routeItem]);
722
730
  };
723
731
  //#endregion
724
- //#region context/FormContext.ts
725
- var FormContext = createContext({ isSubmitting: false });
726
- //#endregion
727
- //#region provider/FormProvider.tsx
728
- var FormProvider = ({ children, isSubmitting }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FormContext.Provider, {
729
- value: { isSubmitting },
730
- children
731
- });
732
- //#endregion
733
- //#region components/Form.tsx
734
- var Form = ({ children, action: actionKey, onSuccess, onError, autoReset = true }) => {
732
+ //#region hooks/useActionParams.ts
733
+ var useActionParams = () => {
735
734
  const invalidate = useInvalidate();
736
735
  const [routeItemData] = useRouteItemData();
737
736
  const [context, setContext] = useContextState();
738
737
  const params = useParams();
739
738
  const { routeItem } = routeItemData;
739
+ return {
740
+ invalidate,
741
+ routeItem,
742
+ latestContext: useLatest(context),
743
+ params,
744
+ setContext
745
+ };
746
+ };
747
+ //#endregion
748
+ //#region components/Form.tsx
749
+ var Form = ({ children, action: actionKey, onSuccess, onError, autoReset = true }) => {
750
+ const { invalidate, routeItem, latestContext, params, setContext } = useActionParams();
740
751
  const [isSubmitting, setIsSubmitting] = useState(false);
741
752
  const onSubmit = async (evt) => {
742
753
  evt.preventDefault();
743
754
  if (!routeItem) throw new Error("Route not found");
744
755
  if (!routeItem.actions) throw new Error("Route action creator not found");
745
756
  const action = routeItem.actions({
746
- context,
757
+ context: latestContext.current,
747
758
  setContext,
748
759
  params,
749
760
  invalidate
@@ -821,53 +832,39 @@ var useBlocker = (blockerFn) => {
821
832
  };
822
833
  //#endregion
823
834
  //#region hooks/useAction.ts
824
- var useAction = (actionKey, onError) => {
825
- const invalidate = useInvalidate();
826
- const [routeItemData] = useRouteItemData();
827
- const [context, setContext] = useContextState();
828
- const params = useParams();
829
- const { routeItem } = routeItemData;
830
- const latestContext = useLatest(context);
835
+ var useAction = (action, options = {}) => {
836
+ const { invalidate, routeItem, latestContext, params, setContext } = useActionParams();
837
+ const latestOnSuccess = useLatest(options?.onSuccess);
838
+ const latestOnError = useLatest(options?.onError);
831
839
  return useCallback(async (formData) => {
832
840
  if (!routeItem) throw new Error("Route not found");
833
841
  if (!routeItem.actions) throw new Error("Route action creator not found");
834
- const action = routeItem.actions({
842
+ const currentAction = routeItem.actions({
835
843
  context: latestContext.current,
836
844
  setContext,
837
845
  params,
838
846
  invalidate
839
- })[actionKey];
840
- if (!action) throw new Error(`Action "${actionKey}" not found`);
847
+ })[action];
848
+ if (!currentAction) throw new Error(`Action "${action}" not found`);
841
849
  try {
842
- await action(formData);
850
+ const result = await currentAction(formData);
843
851
  await invalidate();
852
+ latestOnSuccess.current?.(result);
844
853
  } catch (error) {
845
- onError?.(error);
854
+ latestOnError.current?.(error);
846
855
  }
847
856
  }, [
848
- actionKey,
849
- latestContext,
857
+ action,
850
858
  invalidate,
851
- onError,
859
+ latestContext,
860
+ latestOnError,
861
+ latestOnSuccess,
852
862
  params,
853
863
  routeItem,
854
864
  setContext
855
865
  ]);
856
866
  };
857
867
  //#endregion
858
- //#region hooks/useBeforeUnload.ts
859
- var useBeforeUnload = (callback) => {
860
- useEffect(() => {
861
- const handler = (event) => {
862
- if (!callback) return;
863
- event.preventDefault();
864
- callback();
865
- };
866
- window.addEventListener("beforeunload", handler);
867
- return () => window.removeEventListener("beforeunload", handler);
868
- }, [callback]);
869
- };
870
- //#endregion
871
868
  //#region hooks/useRouterContext.ts
872
869
  var useRouterContext = () => {
873
870
  const [context, setContext] = useContextState();
@@ -962,20 +959,6 @@ function useQueryParam(field, adapter, defaultValue) {
962
959
  ])];
963
960
  }
964
961
  //#endregion
965
- //#region hooks/useHistoricalTrail.ts
966
- var useHistoricalTrail = () => {
967
- const { pathname } = useLocation();
968
- const [trail, setTrail] = useState([]);
969
- useEffect(() => {
970
- if (!pathname) return;
971
- setTrail((prevState) => {
972
- const index = prevState.indexOf(pathname);
973
- return index === -1 ? [...prevState, pathname] : prevState.slice(0, index + 1);
974
- });
975
- }, [pathname]);
976
- return trail;
977
- };
978
- //#endregion
979
962
  //#region hooks/useFormContext.ts
980
963
  var useFormContext = () => useContext(FormContext);
981
964
  //#endregion
@@ -1041,4 +1024,4 @@ var adapter = {
1041
1024
  })
1042
1025
  };
1043
1026
  //#endregion
1044
- export { Form, Link, Router, RouterProvider, adapter, createRouter, useAction, useBeforeUnload, useBlocker, useFormContext, useHistoricalTrail, useInvalidate, useLoaderState, useLocation, useNavigate, useParams, useQueryParam, useRouterContext, useSearchParams };
1027
+ export { Form, Link, Router, RouterProvider, adapter, createRouter, useAction, useBlocker, useFormContext, useInvalidate, useLoaderState, useLocation, useNavigate, useParams, useQueryParam, useRouterContext, useSearchParams };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clear-react-router",
3
- "version": "1.6.9",
3
+ "version": "1.7.0",
4
4
  "description": "A lightweight, type-safe routing library for React applications",
5
5
  "author": "Andrew Bubnov",
6
6
  "scripts": {
@@ -1 +0,0 @@
1
- export declare const useBeforeUnload: (callback?: () => void) => void;
@@ -1 +0,0 @@
1
- export declare const useHistoricalTrail: () => string[];