clear-react-router 1.6.9 → 1.7.1
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 +11 -45
- package/dist/components/Form.d.ts +1 -1
- package/dist/hooks/useAction.d.ts +6 -1
- package/dist/hooks/useGetAction.d.ts +4 -0
- package/dist/index.d.ts +0 -2
- package/dist/index.js +44 -76
- package/package.json +1 -1
- package/dist/hooks/useBeforeUnload.d.ts +0 -1
- package/dist/hooks/useHistoricalTrail.d.ts +0 -1
package/README.md
CHANGED
|
@@ -264,15 +264,15 @@ Actions can be executed declaratively with `<Form />` or imperatively with `useA
|
|
|
264
264
|
|
|
265
265
|
## Form
|
|
266
266
|
|
|
267
|
-
`Form` automatically creates a `FormData` object, executes the specified route action, invalidates the current route, and optionally resets the form.
|
|
267
|
+
`Form` automatically creates a `FormData` object on submit event, executes the specified route action, invalidates the current route, and optionally resets the form, if fields are uncontrolled.
|
|
268
268
|
|
|
269
269
|
`isSubmitting` value available inside the `Form` component from the `useFormContext` hook
|
|
270
270
|
|
|
271
271
|
```tsx
|
|
272
|
-
import { Form, useFormContext } from '
|
|
272
|
+
import { Form, useFormContext } from 'clear-react-router';
|
|
273
273
|
|
|
274
274
|
const SubmitButton = () => {
|
|
275
|
-
const {
|
|
275
|
+
const {isSubmitting} = useFormContext()
|
|
276
276
|
return <button disabled={isSubmitting} type='submit'>Save</button>
|
|
277
277
|
}
|
|
278
278
|
|
|
@@ -305,22 +305,23 @@ 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
|
|
|
311
318
|
const handleClick = async () => {
|
|
312
319
|
const data = new FormData();
|
|
313
|
-
|
|
314
320
|
data.append('title', 'Hello');
|
|
315
|
-
|
|
316
321
|
await save(data);
|
|
317
322
|
};
|
|
318
|
-
```
|
|
319
323
|
|
|
320
|
-
|
|
321
|
-
<button onClick={handleClick}>
|
|
322
|
-
Save
|
|
323
|
-
</button>
|
|
324
|
+
<button onClick={handleClick}>Save</button>
|
|
324
325
|
```
|
|
325
326
|
|
|
326
327
|
`useAction` automatically invalidates the current route after a successful action, causing both `beforeLoad` and `loader` to run again in the background.
|
|
@@ -508,29 +509,6 @@ useEffect(() => {
|
|
|
508
509
|
}, [state, process, reset]);
|
|
509
510
|
```
|
|
510
511
|
|
|
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
512
|
### `useQueryParam()`
|
|
535
513
|
|
|
536
514
|
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 +679,6 @@ function ProductFilter() {
|
|
|
701
679
|
|
|
702
680
|
> **Note:** `getSearchParams` returns `string` for single values, `string[]` for multiple values, and `''` if the key is not found.
|
|
703
681
|
|
|
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
682
|
## Lazy Loading
|
|
717
683
|
|
|
718
684
|
Clear Router supports code-splitting out of the box. Simply pass a function that returns a dynamic import:
|
|
@@ -5,5 +5,5 @@ type FormProps = {
|
|
|
5
5
|
onError?(arg: unknown): void;
|
|
6
6
|
autoReset?: boolean;
|
|
7
7
|
};
|
|
8
|
-
export declare const Form: ({ children, action
|
|
8
|
+
export declare const Form: ({ children, action, onSuccess, onError, autoReset }: PropsWithChildren<FormProps>) => import("react/jsx-runtime").JSX.Element;
|
|
9
9
|
export {};
|
|
@@ -1 +1,6 @@
|
|
|
1
|
-
|
|
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 {};
|
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,44 +723,44 @@ var useInvalidate = () => {
|
|
|
714
723
|
var useParams = () => {
|
|
715
724
|
const [routeItemData] = useRouteItemData();
|
|
716
725
|
const { routeItem, location: { pathname } } = routeItemData;
|
|
717
|
-
|
|
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
|
|
725
|
-
var
|
|
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/useGetAction.ts
|
|
733
|
+
var useGetAction = (actionKey) => {
|
|
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
|
+
const latestContext = useLatest(context);
|
|
740
|
+
if (!routeItem) throw new Error("Route not found");
|
|
741
|
+
if (!routeItem.actions) throw new Error("Route action creator not found");
|
|
742
|
+
const action = routeItem.actions({
|
|
743
|
+
context: latestContext.current,
|
|
744
|
+
setContext,
|
|
745
|
+
params,
|
|
746
|
+
invalidate
|
|
747
|
+
})[actionKey];
|
|
748
|
+
if (!action) throw new Error(`Action "${actionKey}" not found`);
|
|
749
|
+
return {
|
|
750
|
+
currentAction: action,
|
|
751
|
+
invalidate
|
|
752
|
+
};
|
|
753
|
+
};
|
|
754
|
+
//#endregion
|
|
755
|
+
//#region components/Form.tsx
|
|
756
|
+
var Form = ({ children, action, onSuccess, onError, autoReset = true }) => {
|
|
757
|
+
const { currentAction, invalidate } = useGetAction(action);
|
|
740
758
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
741
759
|
const onSubmit = async (evt) => {
|
|
742
760
|
evt.preventDefault();
|
|
743
|
-
if (!routeItem) throw new Error("Route not found");
|
|
744
|
-
if (!routeItem.actions) throw new Error("Route action creator not found");
|
|
745
|
-
const action = routeItem.actions({
|
|
746
|
-
context,
|
|
747
|
-
setContext,
|
|
748
|
-
params,
|
|
749
|
-
invalidate
|
|
750
|
-
})[actionKey];
|
|
751
|
-
if (!action) throw new Error(`Action "${actionKey}" not found`);
|
|
752
761
|
try {
|
|
753
762
|
setIsSubmitting(true);
|
|
754
|
-
const result = await
|
|
763
|
+
const result = await currentAction(new FormData(evt.target));
|
|
755
764
|
await invalidate();
|
|
756
765
|
if (autoReset) evt.target.reset();
|
|
757
766
|
onSuccess?.(result);
|
|
@@ -821,53 +830,26 @@ var useBlocker = (blockerFn) => {
|
|
|
821
830
|
};
|
|
822
831
|
//#endregion
|
|
823
832
|
//#region hooks/useAction.ts
|
|
824
|
-
var useAction = (
|
|
825
|
-
const invalidate =
|
|
826
|
-
const
|
|
827
|
-
const
|
|
828
|
-
const params = useParams();
|
|
829
|
-
const { routeItem } = routeItemData;
|
|
830
|
-
const latestContext = useLatest(context);
|
|
833
|
+
var useAction = (action, options = {}) => {
|
|
834
|
+
const { currentAction, invalidate } = useGetAction(action);
|
|
835
|
+
const latestOnSuccess = useLatest(options?.onSuccess);
|
|
836
|
+
const latestOnError = useLatest(options?.onError);
|
|
831
837
|
return useCallback(async (formData) => {
|
|
832
|
-
if (!routeItem) throw new Error("Route not found");
|
|
833
|
-
if (!routeItem.actions) throw new Error("Route action creator not found");
|
|
834
|
-
const action = routeItem.actions({
|
|
835
|
-
context: latestContext.current,
|
|
836
|
-
setContext,
|
|
837
|
-
params,
|
|
838
|
-
invalidate
|
|
839
|
-
})[actionKey];
|
|
840
|
-
if (!action) throw new Error(`Action "${actionKey}" not found`);
|
|
841
838
|
try {
|
|
842
|
-
await
|
|
839
|
+
const result = await currentAction(formData);
|
|
843
840
|
await invalidate();
|
|
841
|
+
latestOnSuccess.current?.(result);
|
|
844
842
|
} catch (error) {
|
|
845
|
-
|
|
843
|
+
latestOnError.current?.(error);
|
|
846
844
|
}
|
|
847
845
|
}, [
|
|
848
|
-
|
|
849
|
-
latestContext,
|
|
846
|
+
currentAction,
|
|
850
847
|
invalidate,
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
routeItem,
|
|
854
|
-
setContext
|
|
848
|
+
latestOnError,
|
|
849
|
+
latestOnSuccess
|
|
855
850
|
]);
|
|
856
851
|
};
|
|
857
852
|
//#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
853
|
//#region hooks/useRouterContext.ts
|
|
872
854
|
var useRouterContext = () => {
|
|
873
855
|
const [context, setContext] = useContextState();
|
|
@@ -962,20 +944,6 @@ function useQueryParam(field, adapter, defaultValue) {
|
|
|
962
944
|
])];
|
|
963
945
|
}
|
|
964
946
|
//#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
947
|
//#region hooks/useFormContext.ts
|
|
980
948
|
var useFormContext = () => useContext(FormContext);
|
|
981
949
|
//#endregion
|
|
@@ -1041,4 +1009,4 @@ var adapter = {
|
|
|
1041
1009
|
})
|
|
1042
1010
|
};
|
|
1043
1011
|
//#endregion
|
|
1044
|
-
export { Form, Link, Router, RouterProvider, adapter, createRouter, useAction,
|
|
1012
|
+
export { Form, Link, Router, RouterProvider, adapter, createRouter, useAction, useBlocker, useFormContext, useInvalidate, useLoaderState, useLocation, useNavigate, useParams, useQueryParam, useRouterContext, useSearchParams };
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare const useBeforeUnload: (callback?: () => void) => void;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare const useHistoricalTrail: () => string[];
|