@firtoz/router-toolkit 0.1.4 → 0.1.5
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/package.json +13 -4
- package/src/types/Func.ts +1 -1
- package/src/types/HrefArgs.ts +8 -17
- package/src/types/RegisterPages.ts +15 -0
- package/src/types/RoutePath.ts +3 -0
- package/src/useDynamicFetcher.ts +15 -21
- package/src/useDynamicSubmitter.tsx +14 -5
- package/src/useFetcherStateChanged.ts +5 -2
- package/src/types/index.ts +0 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@firtoz/router-toolkit",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "Type-safe React Router 7 framework mode helpers with enhanced fetching, form submission, and state management",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"module": "./src/index.ts",
|
|
@@ -11,7 +11,10 @@
|
|
|
11
11
|
"import": "./src/index.ts",
|
|
12
12
|
"require": "./src/index.ts"
|
|
13
13
|
},
|
|
14
|
-
"./*": [
|
|
14
|
+
"./*": [
|
|
15
|
+
"./src/*.ts",
|
|
16
|
+
"./src/*.tsx"
|
|
17
|
+
]
|
|
15
18
|
},
|
|
16
19
|
"files": [
|
|
17
20
|
"src/**/*",
|
|
@@ -20,8 +23,12 @@
|
|
|
20
23
|
"scripts": {
|
|
21
24
|
"build": "echo 'No build step - using TypeScript source directly'",
|
|
22
25
|
"typecheck": "tsc --noEmit",
|
|
23
|
-
"lint": "
|
|
24
|
-
"
|
|
26
|
+
"lint": "biome check src",
|
|
27
|
+
"format": "biome format src --write",
|
|
28
|
+
"test": "echo 'No tests yet'",
|
|
29
|
+
"changeset": "changeset",
|
|
30
|
+
"version": "changeset version",
|
|
31
|
+
"release": "changeset publish"
|
|
25
32
|
},
|
|
26
33
|
"keywords": [
|
|
27
34
|
"react-router",
|
|
@@ -49,6 +56,8 @@
|
|
|
49
56
|
"zod": "^3.25.69"
|
|
50
57
|
},
|
|
51
58
|
"devDependencies": {
|
|
59
|
+
"@biomejs/biome": "^2.0.6",
|
|
60
|
+
"@changesets/cli": "^2.29.5",
|
|
52
61
|
"@types/react": "^19.1.8",
|
|
53
62
|
"react": "^19.1.0",
|
|
54
63
|
"react-router": "^7.6.3",
|
package/src/types/Func.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// biome-ignore lint/suspicious/noExplicitAny: We really want to use any here
|
|
2
|
-
export type Func = (...args: any[]) => unknown;
|
|
2
|
+
export type Func = (...args: any[]) => unknown;
|
package/src/types/HrefArgs.ts
CHANGED
|
@@ -1,17 +1,8 @@
|
|
|
1
|
-
import type { href
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
type
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
pages: infer Registered extends AnyPages;
|
|
10
|
-
} ? Registered : AnyPages;
|
|
11
|
-
|
|
12
|
-
export type HrefArgs<T extends keyof RegisterPages> = Parameters<typeof href<T>> extends [
|
|
13
|
-
string,
|
|
14
|
-
...infer Rest,
|
|
15
|
-
] ? Rest : [];
|
|
16
|
-
|
|
17
|
-
export type RoutePath<TPath extends keyof RegisterPages> = TPath;
|
|
1
|
+
import type { href } from "react-router";
|
|
2
|
+
import type { RegisterPages } from "./RegisterPages";
|
|
3
|
+
|
|
4
|
+
export type HrefArgs<T extends keyof RegisterPages> = Parameters<
|
|
5
|
+
typeof href<T>
|
|
6
|
+
> extends [string, ...infer Rest]
|
|
7
|
+
? Rest
|
|
8
|
+
: [];
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Register } from "react-router";
|
|
2
|
+
|
|
3
|
+
type AnyParams = Record<string, string | undefined>;
|
|
4
|
+
type AnyPages = Record<
|
|
5
|
+
string,
|
|
6
|
+
{
|
|
7
|
+
params: AnyParams;
|
|
8
|
+
}
|
|
9
|
+
>;
|
|
10
|
+
|
|
11
|
+
export type RegisterPages = Register extends {
|
|
12
|
+
pages: infer Registered extends AnyPages;
|
|
13
|
+
}
|
|
14
|
+
? Registered
|
|
15
|
+
: AnyPages;
|
package/src/useDynamicFetcher.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
2
2
|
import { href, useFetcher, type useLoaderData } from "react-router";
|
|
3
3
|
import type { Func } from "./types/Func";
|
|
4
|
-
import type { HrefArgs
|
|
4
|
+
import type { HrefArgs } from "./types/HrefArgs";
|
|
5
|
+
import type { RegisterPages } from "./types/RegisterPages";
|
|
5
6
|
|
|
6
7
|
type RouteModule = {
|
|
7
8
|
file: keyof RegisterPages;
|
|
@@ -10,7 +11,9 @@ type RouteModule = {
|
|
|
10
11
|
|
|
11
12
|
export const useDynamicFetcher = <TInfo extends RouteModule>(
|
|
12
13
|
path: TInfo["file"],
|
|
13
|
-
...args: TInfo["file"] extends "undefined"
|
|
14
|
+
...args: TInfo["file"] extends "undefined"
|
|
15
|
+
? HrefArgs<"/">
|
|
16
|
+
: HrefArgs<TInfo["file"]>
|
|
14
17
|
): Omit<ReturnType<typeof useFetcher<TInfo["loader"]>>, "load" | "submit"> & {
|
|
15
18
|
load: (queryParams?: Record<string, string>) => Promise<void>;
|
|
16
19
|
} => {
|
|
@@ -49,24 +52,13 @@ export const useDynamicFetcher = <TInfo extends RouteModule>(
|
|
|
49
52
|
const fetchCache = new Map<string, unknown>();
|
|
50
53
|
|
|
51
54
|
// Hook that uses regular fetch instead of useFetcher to avoid route invalidation
|
|
52
|
-
export const useCachedFetch = <
|
|
53
|
-
TInfo
|
|
54
|
-
file: string;
|
|
55
|
-
module: RouteModule;
|
|
56
|
-
},
|
|
57
|
-
>(
|
|
58
|
-
path: TInfo["file"] extends "undefined"
|
|
59
|
-
? "/"
|
|
60
|
-
: `/${TInfo["file"]}` extends keyof RegisterPages
|
|
61
|
-
? `/${TInfo["file"]}`
|
|
62
|
-
: never,
|
|
55
|
+
export const useCachedFetch = <TInfo extends RouteModule>(
|
|
56
|
+
path: TInfo["file"],
|
|
63
57
|
...args: TInfo["file"] extends "undefined"
|
|
64
58
|
? HrefArgs<"/">
|
|
65
|
-
:
|
|
66
|
-
? HrefArgs<`/${TInfo["file"]}`>
|
|
67
|
-
: never
|
|
59
|
+
: HrefArgs<TInfo["file"]>
|
|
68
60
|
): {
|
|
69
|
-
data: ReturnType<typeof useLoaderData<TInfo["
|
|
61
|
+
data: ReturnType<typeof useLoaderData<TInfo["loader"]>> | undefined;
|
|
70
62
|
isLoading: boolean;
|
|
71
63
|
error: Error | undefined;
|
|
72
64
|
} => {
|
|
@@ -83,10 +75,12 @@ export const useCachedFetch = <
|
|
|
83
75
|
const [isLoading, setIsLoading] = useState(false);
|
|
84
76
|
const [error, setError] = useState<Error | undefined>(undefined);
|
|
85
77
|
const [data, setData] = useState<
|
|
86
|
-
ReturnType<typeof useLoaderData<TInfo["
|
|
78
|
+
ReturnType<typeof useLoaderData<TInfo["loader"]>> | undefined
|
|
87
79
|
>(() =>
|
|
88
80
|
fetchCache.has(cacheKey)
|
|
89
|
-
? (fetchCache.get(cacheKey) as ReturnType<
|
|
81
|
+
? (fetchCache.get(cacheKey) as ReturnType<
|
|
82
|
+
typeof useLoaderData<TInfo["loader"]>
|
|
83
|
+
>)
|
|
90
84
|
: undefined,
|
|
91
85
|
);
|
|
92
86
|
|
|
@@ -112,7 +106,7 @@ export const useCachedFetch = <
|
|
|
112
106
|
|
|
113
107
|
// Update cache and state
|
|
114
108
|
fetchCache.set(cacheKey, result);
|
|
115
|
-
setData(result as ReturnType<typeof useLoaderData<TInfo["
|
|
109
|
+
setData(result as ReturnType<typeof useLoaderData<TInfo["loader"]>>);
|
|
116
110
|
} catch (err) {
|
|
117
111
|
setError(err instanceof Error ? err : new Error(String(err)));
|
|
118
112
|
} finally {
|
|
@@ -124,4 +118,4 @@ export const useCachedFetch = <
|
|
|
124
118
|
}, [url, cacheKey]);
|
|
125
119
|
|
|
126
120
|
return { data, isLoading, error };
|
|
127
|
-
};
|
|
121
|
+
};
|
|
@@ -7,8 +7,9 @@ import {
|
|
|
7
7
|
useFetcher,
|
|
8
8
|
} from "react-router";
|
|
9
9
|
import type { z } from "zod/v4";
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
10
|
+
import type { Func } from "./types/Func";
|
|
11
|
+
import type { HrefArgs } from "./types/HrefArgs";
|
|
12
|
+
import type { RegisterPages } from "./types/RegisterPages";
|
|
12
13
|
|
|
13
14
|
type RouteModule = {
|
|
14
15
|
file: keyof RegisterPages;
|
|
@@ -24,15 +25,23 @@ type SubmitFunc<TModule extends RouteModule> = (
|
|
|
24
25
|
) => Promise<void>;
|
|
25
26
|
|
|
26
27
|
type SubmitForm = (
|
|
27
|
-
props: Omit<
|
|
28
|
+
props: Omit<
|
|
29
|
+
FetcherFormProps & React.RefAttributes<HTMLFormElement>,
|
|
30
|
+
"action" | "method"
|
|
31
|
+
> & {
|
|
28
32
|
method: Exclude<SubmitOptions["method"], "GET">;
|
|
29
33
|
},
|
|
30
34
|
) => React.ReactElement;
|
|
31
35
|
|
|
32
36
|
export const useDynamicSubmitter = <TInfo extends RouteModule>(
|
|
33
37
|
path: TInfo["file"],
|
|
34
|
-
...args: TInfo["file"] extends "undefined"
|
|
35
|
-
|
|
38
|
+
...args: TInfo["file"] extends "undefined"
|
|
39
|
+
? HrefArgs<"/">
|
|
40
|
+
: HrefArgs<TInfo["file"]>
|
|
41
|
+
): Omit<
|
|
42
|
+
ReturnType<typeof useFetcher<TInfo["action"]>>,
|
|
43
|
+
"load" | "submit" | "Form"
|
|
44
|
+
> & {
|
|
36
45
|
submit: SubmitFunc<TInfo>;
|
|
37
46
|
Form: SubmitForm;
|
|
38
47
|
} => {
|
|
@@ -8,7 +8,10 @@ import type { useFetcher } from "react-router";
|
|
|
8
8
|
*/
|
|
9
9
|
export const useFetcherStateChanged = (
|
|
10
10
|
fetcher: Pick<ReturnType<typeof useFetcher>, "state">,
|
|
11
|
-
onChange: (
|
|
11
|
+
onChange: (
|
|
12
|
+
lastState: typeof fetcher.state | undefined,
|
|
13
|
+
newState: typeof fetcher.state,
|
|
14
|
+
) => void,
|
|
12
15
|
) => {
|
|
13
16
|
const lastStateRef = useRef<typeof fetcher.state>(fetcher.state);
|
|
14
17
|
|
|
@@ -18,4 +21,4 @@ export const useFetcherStateChanged = (
|
|
|
18
21
|
lastStateRef.current = fetcher.state;
|
|
19
22
|
}
|
|
20
23
|
}, [fetcher.state, onChange]);
|
|
21
|
-
};
|
|
24
|
+
};
|
package/src/types/index.ts
DELETED