Added retry support to the HtmlIsland

This commit is contained in:
Colin Dawson 2026-02-14 11:26:08 +00:00
parent 0e20accee6
commit 7d0a7514ad

View File

@ -2,6 +2,7 @@ import React, { useEffect, useState, useRef, useCallback } from "react";
import { toast } from "react-toastify";
import { useTranslation } from "react-i18next";
import { Namespaces } from "../../i18n/i18n";
import ErrorBlock from "./ErrorBlock";
export interface HtmlIslandProps {
url: string;
@ -20,29 +21,62 @@ const HtmlIsland: React.FC<HtmlIslandProps> = ({
const { t: tIsland } = useTranslation(Namespaces.HtmlIsland);
//
// Load the island HTML
// Retry-capable fetch helper
//
const loadIsland = useCallback(() => {
setError(null);
fetch(url, { credentials: "include" })
.then((r) => {
const fetchWithRetry = useCallback(
async (
attempt: number,
maxAttempts: number,
delayMs: number,
): Promise<string> => {
try {
const r = await fetch(url, { credentials: "include" });
if (!r.ok) throw new Error(`Failed to load island: ${r.status}`);
return r.text();
})
.then((text) => setHtml(text))
.catch(() => {
setError(tIsland("island.loadError"));
toast.error(tIsland("island.loadError"));
});
}, [url, tIsland]);
return await r.text();
} catch (err) {
if (attempt >= maxAttempts) {
throw err;
}
const jitter = Math.random() * 150;
const nextDelay = delayMs * Math.pow(2, attempt) + jitter;
await new Promise((resolve) => setTimeout(resolve, nextDelay));
return fetchWithRetry(attempt + 1, maxAttempts, delayMs);
}
},
[url],
);
//
// Initial load + reload when URL changes
// Initial load + reload when URL changes (with retry + cancellation)
//
useEffect(() => {
loadIsland();
}, [loadIsland]);
let cancelled = false;
const load = async () => {
setError(null);
try {
const text = await fetchWithRetry(0, 4, 300);
if (!cancelled) {
setHtml(text);
}
} catch {
if (!cancelled) {
setError(tIsland("island.loadError"));
toast.error(tIsland("island.loadError"));
}
}
};
load();
return () => {
cancelled = true;
};
}, [url, tIsland, fetchWithRetry]);
//
// After HTML is injected, run scripts + bind form handling
@ -98,8 +132,7 @@ const HtmlIsland: React.FC<HtmlIslandProps> = ({
form.getAttribute("data-full-page-redirect") === "true";
if (shouldFullPageRedirect) {
// Do NOT intercept this form let the browser handle POST + redirect
return;
return; // allow normal POST + redirect
}
const submitHandler = async (e: Event) => {
@ -131,7 +164,14 @@ const HtmlIsland: React.FC<HtmlIslandProps> = ({
toast.success(
successMessage ? successMessage : tIsland("island.saveSuccess"),
);
loadIsland();
// reload island
try {
const text = await fetchWithRetry(0, 4, 300);
setHtml(text);
} catch {
setError(tIsland("island.loadError"));
toast.error(tIsland("island.loadError"));
}
return;
}
@ -150,28 +190,14 @@ const HtmlIsland: React.FC<HtmlIslandProps> = ({
form.addEventListener("submit", submitHandler);
// Cleanup on re-render
return () => {
form.removeEventListener("submit", submitHandler);
};
}, [html, islandId, successMessage, tIsland, loadIsland]);
}, [html, islandId, successMessage, tIsland, fetchWithRetry]);
return (
<div>
{error && (
<div
style={{
background: "#fee",
border: "1px solid #c00",
padding: "8px",
marginBottom: "10px",
borderRadius: "4px",
color: "#900",
}}
>
{error}
</div>
)}
<ErrorBlock error={error ?? undefined} />
<div ref={containerRef} dangerouslySetInnerHTML={{ __html: html }} />
</div>