toiljs 0.0.14 → 0.0.16
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/.babelrc +13 -13
- package/.gitattributes +2 -2
- package/.github/ISSUE_TEMPLATE/bug_report.md +38 -38
- package/.github/ISSUE_TEMPLATE/bug_report.yml +90 -90
- package/.github/ISSUE_TEMPLATE/config.yml +8 -8
- package/.github/ISSUE_TEMPLATE/feature_request.md +20 -20
- package/.github/PULL_REQUEST_TEMPLATE.md +43 -43
- package/.github/changelog-config.json +45 -45
- package/.github/dependabot.yml +27 -27
- package/.github/workflows/ci.yml +191 -191
- package/.prettierrc.json +11 -11
- package/.vscode/settings.json +9 -9
- package/CHANGELOG.md +5 -5
- package/LICENSE +187 -187
- package/README.md +339 -315
- package/as-pect.asconfig.json +34 -34
- package/as-pect.config.js +65 -65
- package/assets/logo.svg +36 -36
- package/build/backend/.tsbuildinfo +1 -1
- package/build/cli/.tsbuildinfo +1 -1
- package/build/cli/index.js +2926 -191
- package/build/client/.tsbuildinfo +1 -1
- package/build/client/dev/devtools.d.ts +6 -0
- package/build/client/dev/devtools.js +442 -0
- package/build/client/dev/error-overlay.d.ts +9 -0
- package/build/client/dev/error-overlay.js +19 -4
- package/build/client/head/metadata.d.ts +3 -1
- package/build/client/head/metadata.js +8 -0
- package/build/client/index.d.ts +4 -4
- package/build/client/index.js +2 -2
- package/build/client/navigation/navigation.d.ts +2 -0
- package/build/client/navigation/navigation.js +9 -1
- package/build/client/navigation/prefetch.d.ts +1 -0
- package/build/client/navigation/prefetch.js +35 -0
- package/build/client/routing/Router.js +1 -1
- package/build/client/routing/hooks.js +6 -2
- package/build/client/routing/loader.d.ts +25 -0
- package/build/client/routing/loader.js +53 -7
- package/build/client/routing/mount.js +4 -3
- package/build/compiler/.tsbuildinfo +1 -1
- package/build/compiler/config.d.ts +18 -0
- package/build/compiler/config.js +8 -0
- package/build/compiler/docs.js +16 -16
- package/build/compiler/generate.js +3 -0
- package/build/compiler/index.d.ts +2 -2
- package/build/compiler/index.js +3 -1
- package/build/compiler/plugin.js +156 -0
- package/build/compiler/prerender.d.ts +1 -0
- package/build/compiler/prerender.js +2 -1
- package/build/compiler/seo.d.ts +2 -2
- package/build/compiler/seo.js +8 -6
- package/build/compiler/ssg.d.ts +5 -0
- package/build/compiler/ssg.js +121 -0
- package/build/io/.tsbuildinfo +1 -1
- package/build/logger/.tsbuildinfo +1 -1
- package/build/shared/.tsbuildinfo +1 -1
- package/eslint.config.js +48 -48
- package/examples/basic/client/404.tsx +11 -11
- package/examples/basic/client/components/.gitkeep +1 -1
- package/examples/basic/client/global-error.tsx +13 -13
- package/examples/basic/client/layout.tsx +25 -25
- package/examples/basic/client/public/images/.gitkeep +1 -1
- package/examples/basic/client/public/images/logo.svg +36 -36
- package/examples/basic/client/public/robots.txt +2 -2
- package/examples/basic/client/routes/docs/[...slug].tsx +12 -12
- package/examples/basic/client/routes/features/error/error.tsx +16 -16
- package/examples/basic/client/routes/features/template/b.tsx +14 -14
- package/examples/basic/client/routes/files/[[...slug]].tsx +21 -21
- package/examples/basic/client/routes/gallery/layout.tsx +13 -13
- package/examples/basic/client/routes/io.tsx +24 -24
- package/examples/basic/client/routes/loader-demo/loading.tsx +13 -13
- package/examples/basic/client/routes/search.tsx +61 -61
- package/examples/basic/client/toil.tsx +5 -5
- package/package.json +155 -147
- package/presets/eslint.js +88 -88
- package/presets/no-uint8array-tostring.js +200 -200
- package/presets/prettier.json +18 -18
- package/presets/tsconfig.json +37 -37
- package/src/backend/index.ts +160 -160
- package/src/cli/proc.ts +50 -50
- package/src/cli/updates.ts +69 -69
- package/src/cli/validate.ts +31 -31
- package/src/client/channel/channel.ts +146 -146
- package/src/client/components/Form.tsx +65 -65
- package/src/client/components/Script.tsx +113 -113
- package/src/client/components/Slot.tsx +21 -21
- package/src/client/dev/devtools.tsx +973 -0
- package/src/client/dev/error-overlay.tsx +30 -4
- package/src/client/head/head.ts +167 -167
- package/src/client/head/metadata.ts +19 -1
- package/src/client/index.ts +19 -9
- package/src/client/navigation/NavLink.tsx +86 -86
- package/src/client/navigation/navigation.ts +25 -5
- package/src/client/navigation/prefetch.ts +169 -130
- package/src/client/navigation/scroll.ts +53 -53
- package/src/client/routing/Router.tsx +8 -2
- package/src/client/routing/action.ts +122 -122
- package/src/client/routing/error-boundary.tsx +43 -43
- package/src/client/routing/hooks.ts +21 -6
- package/src/client/routing/loader.ts +325 -225
- package/src/client/routing/match.ts +47 -47
- package/src/client/routing/mount.tsx +54 -52
- package/src/client/routing/params-context.ts +10 -10
- package/src/client/routing/slot-context.ts +7 -7
- package/src/client/search/search.ts +189 -189
- package/src/client/search/use-page-search.ts +73 -73
- package/src/client/types.ts +73 -73
- package/src/compiler/config.ts +47 -1
- package/src/compiler/docs.ts +228 -228
- package/src/compiler/generate.ts +394 -391
- package/src/compiler/index.ts +64 -54
- package/src/compiler/pages.ts +70 -70
- package/src/compiler/plugin.ts +170 -2
- package/src/compiler/prerender.ts +5 -1
- package/src/compiler/seo.ts +23 -7
- package/src/compiler/ssg.ts +162 -0
- package/src/io/BinaryReader.ts +340 -340
- package/src/io/BinaryWriter.ts +385 -385
- package/src/io/FastMap.ts +127 -127
- package/src/io/index.ts +11 -11
- package/src/io/lengths.ts +14 -14
- package/src/io/types.ts +18 -18
- package/src/logger/index.ts +22 -22
- package/src/server/index.ts +10 -10
- package/src/server/main.ts +13 -13
- package/src/server/tsconfig.json +4 -4
- package/src/shared/index.ts +10 -10
- package/std/client/index.d.ts +15 -15
- package/std/client/package.json +3 -3
- package/test/assembly/example.spec.ts +7 -7
- package/test/channel.test.ts +21 -21
- package/test/dom/Link.test.tsx +47 -47
- package/test/dom/NavLink.test.tsx +37 -37
- package/test/dom/error-overlay.test.tsx +44 -44
- package/test/dom/loader.test.tsx +121 -121
- package/test/dom/navigation.test.ts +59 -59
- package/test/dom/revalidate.test.tsx +38 -38
- package/test/dom/route-head.test.tsx +78 -78
- package/test/dom/router-loading.test.tsx +44 -44
- package/test/dom/scroll.test.ts +56 -56
- package/test/dom/use-metadata.test.tsx +58 -0
- package/test/io.test.ts +93 -93
- package/test/navlink.test.ts +28 -28
- package/test/placeholder.test.ts +9 -9
- package/test/routes.test.ts +76 -76
- package/test/seo.test.ts +175 -164
- package/test/slot-layouts.test.ts +69 -69
- package/test/ssg.test.ts +36 -0
- package/test/update.test.ts +44 -44
- package/test/validate.test.ts +42 -42
- package/toil-routes.d.ts +7 -0
- package/toilconfig.json +30 -30
- package/tsconfig.backend.json +13 -13
- package/tsconfig.base.json +35 -35
- package/tsconfig.cli.json +13 -13
- package/tsconfig.client.json +14 -14
- package/tsconfig.compiler.json +13 -13
- package/tsconfig.io.json +12 -12
- package/tsconfig.json +22 -22
- package/tsconfig.logger.json +12 -12
- package/tsconfig.server.json +10 -10
- package/tsconfig.shared.json +12 -12
- package/vitest.config.ts +26 -26
- package/.idea/codeStyles/Project.xml +0 -54
- package/.idea/codeStyles/codeStyleConfig.xml +0 -5
- package/.idea/inspectionProfiles/Project_Default.xml +0 -6
- package/.idea/modules.xml +0 -8
- package/.idea/prettier.xml +0 -7
- package/.idea/toiljs.iml +0 -8
- package/.idea/vcs.xml +0 -6
- package/.toil/entry.tsx +0 -9
- package/.toil/index.html +0 -12
- package/.toil/routes.ts +0 -9
- package/build/cli/configure.d.ts +0 -16
- package/build/cli/configure.js +0 -272
- package/build/cli/create.d.ts +0 -16
- package/build/cli/create.js +0 -420
- package/build/cli/diagnostics.d.ts +0 -55
- package/build/cli/diagnostics.js +0 -333
- package/build/cli/doctor.d.ts +0 -6
- package/build/cli/doctor.js +0 -249
- package/build/cli/features.d.ts +0 -25
- package/build/cli/features.js +0 -107
- package/build/cli/index.d.ts +0 -2
- package/build/cli/proc.d.ts +0 -6
- package/build/cli/proc.js +0 -31
- package/build/cli/ui.d.ts +0 -9
- package/build/cli/ui.js +0 -75
- package/build/cli/update.d.ts +0 -7
- package/build/cli/update.js +0 -117
- package/build/cli/updates.d.ts +0 -10
- package/build/cli/updates.js +0 -45
- package/build/cli/validate.d.ts +0 -4
- package/build/cli/validate.js +0 -19
- package/build/client/Link.d.ts +0 -8
- package/build/client/Link.js +0 -44
- package/build/client/NavLink.d.ts +0 -14
- package/build/client/NavLink.js +0 -37
- package/build/client/Router.d.ts +0 -7
- package/build/client/Router.js +0 -55
- package/build/client/channel.d.ts +0 -23
- package/build/client/channel.js +0 -94
- package/build/client/error-boundary.d.ts +0 -16
- package/build/client/error-boundary.js +0 -19
- package/build/client/head.d.ts +0 -26
- package/build/client/head.js +0 -87
- package/build/client/hooks.d.ts +0 -17
- package/build/client/hooks.js +0 -48
- package/build/client/lazy.d.ts +0 -16
- package/build/client/lazy.js +0 -53
- package/build/client/match.d.ts +0 -2
- package/build/client/match.js +0 -32
- package/build/client/mount.d.ts +0 -2
- package/build/client/mount.js +0 -13
- package/build/client/navigation.d.ts +0 -13
- package/build/client/navigation.js +0 -97
- package/build/client/params-context.d.ts +0 -2
- package/build/client/params-context.js +0 -2
- package/build/client/prefetch.d.ts +0 -11
- package/build/client/prefetch.js +0 -100
- package/build/client/runtime.d.ts +0 -31
- package/build/client/runtime.js +0 -112
- package/build/client/scroll.d.ts +0 -8
- package/build/client/scroll.js +0 -36
- package/toil-env.d.ts +0 -16
|
@@ -1,122 +1,122 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Mutations (writes), the counterpart to loaders (reads). A loader fetches data on navigation;
|
|
3
|
-
* an action performs a write (save, delete, a server/WASM call) on demand, then revalidates the
|
|
4
|
-
* affected loader data so the UI reflects the change. `useAction` tracks pending/error/result state;
|
|
5
|
-
* `<Form>` is sugar over it for the form case.
|
|
6
|
-
*/
|
|
7
|
-
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
8
|
-
|
|
9
|
-
import { invalidateLoaderData } from './loader.js';
|
|
10
|
-
import { refresh } from '../navigation/navigation.js';
|
|
11
|
-
import type { Href } from '../types.js';
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Which loader data to refetch after an action succeeds:
|
|
15
|
-
* - `true` (default), the current route.
|
|
16
|
-
* - an `Href` (or array), those specific routes.
|
|
17
|
-
* - `false`, nothing.
|
|
18
|
-
*/
|
|
19
|
-
export type RevalidateTarget = boolean | Href | readonly Href[];
|
|
20
|
-
|
|
21
|
-
/** Options for {@link useAction}. */
|
|
22
|
-
export interface UseActionOptions<TData> {
|
|
23
|
-
/** Loader data to revalidate after success. Default `true` (the current route). */
|
|
24
|
-
readonly revalidate?: RevalidateTarget;
|
|
25
|
-
/** Called after a successful run, with the action's return value. */
|
|
26
|
-
readonly onSuccess?: (data: TData) => void;
|
|
27
|
-
/** Called when the action throws. */
|
|
28
|
-
readonly onError?: (error: unknown) => void;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/** Live state of an action. */
|
|
32
|
-
export interface ActionState<TData> {
|
|
33
|
-
/** True while a run is in flight. */
|
|
34
|
-
readonly pending: boolean;
|
|
35
|
-
/** The error from the last failed run, or `undefined`. */
|
|
36
|
-
readonly error: unknown;
|
|
37
|
-
/** The value returned by the last successful run, or `undefined`. */
|
|
38
|
-
readonly data: TData | undefined;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/** Handle returned by {@link useAction}: current state plus `run` / `reset`. */
|
|
42
|
-
export interface ActionHandle<TInput, TData> extends ActionState<TData> {
|
|
43
|
-
/**
|
|
44
|
-
* Run the action. Resolves to the result on success, or `undefined` if it threw (the error is
|
|
45
|
-
* captured in `error` instead of rejecting, so a fire-and-forget `onClick` can't leak an
|
|
46
|
-
* unhandled rejection).
|
|
47
|
-
*/
|
|
48
|
-
run: (input: TInput) => Promise<TData | undefined>;
|
|
49
|
-
/** Reset back to idle (clears `pending` / `error` / `data`). */
|
|
50
|
-
reset: () => void;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/** Refetches loader data per a {@link RevalidateTarget}, then re-renders once. */
|
|
54
|
-
function applyRevalidate(target: RevalidateTarget | undefined): void {
|
|
55
|
-
if (target === false) return;
|
|
56
|
-
if (target === undefined || target === true) {
|
|
57
|
-
invalidateLoaderData();
|
|
58
|
-
} else {
|
|
59
|
-
const hrefs = typeof target === 'string' ? [target] : target;
|
|
60
|
-
for (const href of hrefs) invalidateLoaderData(href);
|
|
61
|
-
}
|
|
62
|
-
refresh();
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Runs a mutation with pending/error/result tracking, revalidating loader data on success. Example:
|
|
67
|
-
*
|
|
68
|
-
* ```ts
|
|
69
|
-
* const save = useAction((title: string) => api.save(title), { revalidate: true });
|
|
70
|
-
* <button disabled={save.pending} onClick={() => void save.run(title)}>Save</button>
|
|
71
|
-
* ```
|
|
72
|
-
*/
|
|
73
|
-
export function useAction<TInput = void, TData = unknown>(
|
|
74
|
-
fn: (input: TInput) => TData | Promise<TData>,
|
|
75
|
-
options: UseActionOptions<TData> = {},
|
|
76
|
-
): ActionHandle<TInput, TData> {
|
|
77
|
-
const [state, setState] = useState<ActionState<TData>>({
|
|
78
|
-
pending: false,
|
|
79
|
-
error: undefined,
|
|
80
|
-
data: undefined,
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
// Hold the latest fn/options so `run` keeps a stable identity across renders.
|
|
84
|
-
const latest = useRef({ fn, options });
|
|
85
|
-
latest.current = { fn, options };
|
|
86
|
-
const runId = useRef(0);
|
|
87
|
-
const mounted = useRef(true);
|
|
88
|
-
useEffect(
|
|
89
|
-
() => () => {
|
|
90
|
-
mounted.current = false;
|
|
91
|
-
},
|
|
92
|
-
[],
|
|
93
|
-
);
|
|
94
|
-
|
|
95
|
-
const run = useCallback(async (input: TInput): Promise<TData | undefined> => {
|
|
96
|
-
const id = ++runId.current;
|
|
97
|
-
setState((s) => ({ ...s, pending: true, error: undefined }));
|
|
98
|
-
try {
|
|
99
|
-
const data = await latest.current.fn(input);
|
|
100
|
-
// Ignore a stale run that a newer one (or unmount) has superseded.
|
|
101
|
-
if (mounted.current && id === runId.current) {
|
|
102
|
-
setState({ pending: false, error: undefined, data });
|
|
103
|
-
}
|
|
104
|
-
applyRevalidate(latest.current.options.revalidate);
|
|
105
|
-
latest.current.options.onSuccess?.(data);
|
|
106
|
-
return data;
|
|
107
|
-
} catch (error) {
|
|
108
|
-
if (mounted.current && id === runId.current) {
|
|
109
|
-
setState({ pending: false, error, data: undefined });
|
|
110
|
-
}
|
|
111
|
-
latest.current.options.onError?.(error);
|
|
112
|
-
return undefined;
|
|
113
|
-
}
|
|
114
|
-
}, []);
|
|
115
|
-
|
|
116
|
-
const reset = useCallback(() => {
|
|
117
|
-
runId.current += 1;
|
|
118
|
-
setState({ pending: false, error: undefined, data: undefined });
|
|
119
|
-
}, []);
|
|
120
|
-
|
|
121
|
-
return { ...state, run, reset };
|
|
122
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Mutations (writes), the counterpart to loaders (reads). A loader fetches data on navigation;
|
|
3
|
+
* an action performs a write (save, delete, a server/WASM call) on demand, then revalidates the
|
|
4
|
+
* affected loader data so the UI reflects the change. `useAction` tracks pending/error/result state;
|
|
5
|
+
* `<Form>` is sugar over it for the form case.
|
|
6
|
+
*/
|
|
7
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
8
|
+
|
|
9
|
+
import { invalidateLoaderData } from './loader.js';
|
|
10
|
+
import { refresh } from '../navigation/navigation.js';
|
|
11
|
+
import type { Href } from '../types.js';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Which loader data to refetch after an action succeeds:
|
|
15
|
+
* - `true` (default), the current route.
|
|
16
|
+
* - an `Href` (or array), those specific routes.
|
|
17
|
+
* - `false`, nothing.
|
|
18
|
+
*/
|
|
19
|
+
export type RevalidateTarget = boolean | Href | readonly Href[];
|
|
20
|
+
|
|
21
|
+
/** Options for {@link useAction}. */
|
|
22
|
+
export interface UseActionOptions<TData> {
|
|
23
|
+
/** Loader data to revalidate after success. Default `true` (the current route). */
|
|
24
|
+
readonly revalidate?: RevalidateTarget;
|
|
25
|
+
/** Called after a successful run, with the action's return value. */
|
|
26
|
+
readonly onSuccess?: (data: TData) => void;
|
|
27
|
+
/** Called when the action throws. */
|
|
28
|
+
readonly onError?: (error: unknown) => void;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Live state of an action. */
|
|
32
|
+
export interface ActionState<TData> {
|
|
33
|
+
/** True while a run is in flight. */
|
|
34
|
+
readonly pending: boolean;
|
|
35
|
+
/** The error from the last failed run, or `undefined`. */
|
|
36
|
+
readonly error: unknown;
|
|
37
|
+
/** The value returned by the last successful run, or `undefined`. */
|
|
38
|
+
readonly data: TData | undefined;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Handle returned by {@link useAction}: current state plus `run` / `reset`. */
|
|
42
|
+
export interface ActionHandle<TInput, TData> extends ActionState<TData> {
|
|
43
|
+
/**
|
|
44
|
+
* Run the action. Resolves to the result on success, or `undefined` if it threw (the error is
|
|
45
|
+
* captured in `error` instead of rejecting, so a fire-and-forget `onClick` can't leak an
|
|
46
|
+
* unhandled rejection).
|
|
47
|
+
*/
|
|
48
|
+
run: (input: TInput) => Promise<TData | undefined>;
|
|
49
|
+
/** Reset back to idle (clears `pending` / `error` / `data`). */
|
|
50
|
+
reset: () => void;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Refetches loader data per a {@link RevalidateTarget}, then re-renders once. */
|
|
54
|
+
function applyRevalidate(target: RevalidateTarget | undefined): void {
|
|
55
|
+
if (target === false) return;
|
|
56
|
+
if (target === undefined || target === true) {
|
|
57
|
+
invalidateLoaderData();
|
|
58
|
+
} else {
|
|
59
|
+
const hrefs = typeof target === 'string' ? [target] : target;
|
|
60
|
+
for (const href of hrefs) invalidateLoaderData(href);
|
|
61
|
+
}
|
|
62
|
+
refresh();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Runs a mutation with pending/error/result tracking, revalidating loader data on success. Example:
|
|
67
|
+
*
|
|
68
|
+
* ```ts
|
|
69
|
+
* const save = useAction((title: string) => api.save(title), { revalidate: true });
|
|
70
|
+
* <button disabled={save.pending} onClick={() => void save.run(title)}>Save</button>
|
|
71
|
+
* ```
|
|
72
|
+
*/
|
|
73
|
+
export function useAction<TInput = void, TData = unknown>(
|
|
74
|
+
fn: (input: TInput) => TData | Promise<TData>,
|
|
75
|
+
options: UseActionOptions<TData> = {},
|
|
76
|
+
): ActionHandle<TInput, TData> {
|
|
77
|
+
const [state, setState] = useState<ActionState<TData>>({
|
|
78
|
+
pending: false,
|
|
79
|
+
error: undefined,
|
|
80
|
+
data: undefined,
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// Hold the latest fn/options so `run` keeps a stable identity across renders.
|
|
84
|
+
const latest = useRef({ fn, options });
|
|
85
|
+
latest.current = { fn, options };
|
|
86
|
+
const runId = useRef(0);
|
|
87
|
+
const mounted = useRef(true);
|
|
88
|
+
useEffect(
|
|
89
|
+
() => () => {
|
|
90
|
+
mounted.current = false;
|
|
91
|
+
},
|
|
92
|
+
[],
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
const run = useCallback(async (input: TInput): Promise<TData | undefined> => {
|
|
96
|
+
const id = ++runId.current;
|
|
97
|
+
setState((s) => ({ ...s, pending: true, error: undefined }));
|
|
98
|
+
try {
|
|
99
|
+
const data = await latest.current.fn(input);
|
|
100
|
+
// Ignore a stale run that a newer one (or unmount) has superseded.
|
|
101
|
+
if (mounted.current && id === runId.current) {
|
|
102
|
+
setState({ pending: false, error: undefined, data });
|
|
103
|
+
}
|
|
104
|
+
applyRevalidate(latest.current.options.revalidate);
|
|
105
|
+
latest.current.options.onSuccess?.(data);
|
|
106
|
+
return data;
|
|
107
|
+
} catch (error) {
|
|
108
|
+
if (mounted.current && id === runId.current) {
|
|
109
|
+
setState({ pending: false, error, data: undefined });
|
|
110
|
+
}
|
|
111
|
+
latest.current.options.onError?.(error);
|
|
112
|
+
return undefined;
|
|
113
|
+
}
|
|
114
|
+
}, []);
|
|
115
|
+
|
|
116
|
+
const reset = useCallback(() => {
|
|
117
|
+
runId.current += 1;
|
|
118
|
+
setState({ pending: false, error: undefined, data: undefined });
|
|
119
|
+
}, []);
|
|
120
|
+
|
|
121
|
+
return { ...state, run, reset };
|
|
122
|
+
}
|
|
@@ -1,43 +1,43 @@
|
|
|
1
|
-
import { Component, Suspense, type ComponentType, type ReactNode } from 'react';
|
|
2
|
-
|
|
3
|
-
import type { RouteErrorProps } from '../types.js';
|
|
4
|
-
|
|
5
|
-
interface ErrorBoundaryProps {
|
|
6
|
-
readonly fallback: ComponentType<RouteErrorProps>;
|
|
7
|
-
readonly children: ReactNode;
|
|
8
|
-
}
|
|
9
|
-
interface ErrorBoundaryState {
|
|
10
|
-
readonly error: Error | null;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Catches render errors in its subtree and shows the route's `error.tsx` (with a `reset` to retry).
|
|
15
|
-
* Error boundaries must be class components, React has no hook equivalent.
|
|
16
|
-
*/
|
|
17
|
-
export class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
|
|
18
|
-
state: ErrorBoundaryState = { error: null };
|
|
19
|
-
|
|
20
|
-
static getDerivedStateFromError(error: Error): ErrorBoundaryState {
|
|
21
|
-
return { error };
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
reset = (): void => {
|
|
25
|
-
this.setState({ error: null });
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
render(): ReactNode {
|
|
29
|
-
const { error } = this.state;
|
|
30
|
-
if (error) {
|
|
31
|
-
const Fallback = this.props.fallback;
|
|
32
|
-
return (
|
|
33
|
-
<Suspense fallback={null}>
|
|
34
|
-
<Fallback
|
|
35
|
-
error={error}
|
|
36
|
-
reset={this.reset}
|
|
37
|
-
/>
|
|
38
|
-
</Suspense>
|
|
39
|
-
);
|
|
40
|
-
}
|
|
41
|
-
return this.props.children;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
1
|
+
import { Component, Suspense, type ComponentType, type ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
import type { RouteErrorProps } from '../types.js';
|
|
4
|
+
|
|
5
|
+
interface ErrorBoundaryProps {
|
|
6
|
+
readonly fallback: ComponentType<RouteErrorProps>;
|
|
7
|
+
readonly children: ReactNode;
|
|
8
|
+
}
|
|
9
|
+
interface ErrorBoundaryState {
|
|
10
|
+
readonly error: Error | null;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Catches render errors in its subtree and shows the route's `error.tsx` (with a `reset` to retry).
|
|
15
|
+
* Error boundaries must be class components, React has no hook equivalent.
|
|
16
|
+
*/
|
|
17
|
+
export class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
|
|
18
|
+
state: ErrorBoundaryState = { error: null };
|
|
19
|
+
|
|
20
|
+
static getDerivedStateFromError(error: Error): ErrorBoundaryState {
|
|
21
|
+
return { error };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
reset = (): void => {
|
|
25
|
+
this.setState({ error: null });
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
render(): ReactNode {
|
|
29
|
+
const { error } = this.state;
|
|
30
|
+
if (error) {
|
|
31
|
+
const Fallback = this.props.fallback;
|
|
32
|
+
return (
|
|
33
|
+
<Suspense fallback={null}>
|
|
34
|
+
<Fallback
|
|
35
|
+
error={error}
|
|
36
|
+
reset={this.reset}
|
|
37
|
+
/>
|
|
38
|
+
</Suspense>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
return this.props.children;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -2,7 +2,14 @@
|
|
|
2
2
|
* Router hooks for user route components: read the params / pathname / search params, navigate
|
|
3
3
|
* imperatively, and grab a router handle.
|
|
4
4
|
*/
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
startTransition,
|
|
7
|
+
useContext,
|
|
8
|
+
useEffect,
|
|
9
|
+
useMemo,
|
|
10
|
+
useReducer,
|
|
11
|
+
useSyncExternalStore,
|
|
12
|
+
} from 'react';
|
|
6
13
|
|
|
7
14
|
import type { RouteParams } from './match.js';
|
|
8
15
|
import {
|
|
@@ -76,14 +83,22 @@ export function useRouter(): RouterInstance {
|
|
|
76
83
|
}
|
|
77
84
|
|
|
78
85
|
/**
|
|
79
|
-
* Subscribes to location changes and reads the live `window.location` on render.
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
86
|
+
* Subscribes to location changes and reads the live `window.location` on render. The re-render runs
|
|
87
|
+
* inside `startTransition` so React keeps the current page visible while the next route's chunk and
|
|
88
|
+
* loader resolve (committing the new tree only once it's ready), instead of committing a suspended
|
|
89
|
+
* tree and flashing an empty page on every link click.
|
|
83
90
|
*/
|
|
84
91
|
function useLocationSubscription(): void {
|
|
85
92
|
const [, forceUpdate] = useReducer((n: number): number => n + 1, 0);
|
|
86
|
-
useEffect(
|
|
93
|
+
useEffect(
|
|
94
|
+
() =>
|
|
95
|
+
subscribeLocation(() => {
|
|
96
|
+
startTransition(() => {
|
|
97
|
+
forceUpdate();
|
|
98
|
+
});
|
|
99
|
+
}),
|
|
100
|
+
[],
|
|
101
|
+
);
|
|
87
102
|
}
|
|
88
103
|
|
|
89
104
|
/** Subscribes to and returns the current `location.pathname`. */
|