返回 CodeWhale
retry-action.tsx
根目录 / web / components / retry-action.tsx
1 "use client";
2
3 import { useRouter } from "next/navigation";
4 import { useState, useTransition } from "react";
5
6 /**
7 * The one retry control for shared error states. Two modes:
8 * - `onRetry` — call the caller's recovery (an error boundary's `reset`,
9 * a re-fetch, a re-probe);
10 * - no `onRetry` — re-run the server render for this route via
11 * `router.refresh()`, which is the honest retry for a page whose data
12 * is fetched on the server.
13 *
14 * `aria-busy` while the retry is in flight; the label never changes, so
15 * a screen reader hears one control, not a sequence of them.
16 */
17 export function RetryAction({
18 label,
19 onRetry,
20 variant = "primary",
21 }: {
22 label: string;
23 onRetry?: () => void | Promise<void>;
24 variant?: "primary" | "secondary";
25 }) {
26 const router = useRouter();
27 const [pending, startTransition] = useTransition();
28 const [busy, setBusy] = useState(false);
29
30 const run = async () => {
31 if (onRetry) {
32 setBusy(true);
33 try {
34 await onRetry();
35 } finally {
36 setBusy(false);
37 }
38 return;
39 }
40 startTransition(() => router.refresh());
41 };
42
43 const inFlight = pending || busy;
44 return (
45 <button
46 type="button"
47 onClick={run}
48 className={`portal-button portal-button-${variant} state-retry`}
49 aria-busy={inFlight}
50 disabled={inFlight}
51 >
52 {label}
53 </button>
54 );
55 }
56
56 lines Plain Text