{"record":{"id":"cbd2a6e28034a356","repo":"decolua/9router","slug":"http-res-status-cbd2a6","errorCode":null,"errorMessage":"`HTTP ${res.status}`","messagePattern":"`HTTP (.+?)`","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"src/shared/components/DonateModal.js","lineNumber":20,"sourceCode":"\nimport { useEffect, useState, useRef } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport PropTypes from \"prop-types\";\nimport { GITHUB_CONFIG } from \"@/shared/constants/config\";\n\nexport default function DonateModal({ isOpen, onClose }) {\n  const [data, setData] = useState(null);\n  const [loading, setLoading] = useState(false);\n  const [error, setError] = useState(\"\");\n  const modalRef = useRef(null);\n\n  useEffect(() => {\n    if (!isOpen || data) return;\n    setLoading(true);\n    setError(\"\");\n    fetch(GITHUB_CONFIG.donateUrl, { cache: \"no-store\" })\n      .then((res) => {\n        if (!res.ok) throw new Error(`HTTP ${res.status}`);\n        return res.json();\n      })\n      .then((json) => setData(json))\n      .catch((err) => setError(err.message || \"Failed to load\"))\n      .finally(() => setLoading(false));\n  }, [isOpen, data]);\n\n  useEffect(() => {\n    const handleClickOutside = (e) => {\n      if (modalRef.current && !modalRef.current.contains(e.target)) onClose();\n    };\n    if (isOpen) {\n      document.addEventListener(\"mousedown\", handleClickOutside);\n      return () => document.removeEventListener(\"mousedown\", handleClickOutside);\n    }\n  }, [isOpen, onClose]);\n\n  if (!isOpen || typeof document === \"undefined\") return null;","sourceCodeStart":2,"sourceCodeEnd":38,"githubUrl":"https://github.com/decolua/9router/blob/90b52e06ffd666b7929554211474d01588f6b1f8/src/shared/components/DonateModal.js#L2-L38","documentation":"DonateModal fetches JSON (sponsor/donate data) from GITHUB_CONFIG.donateUrl with cache:'no-store' and throws `HTTP ${res.status}` for any non-ok response. The effect catches it and shows the status string as the modal error. Like ChangelogModal this is a raw status passthrough with no response body detail.","triggerScenarios":"fetch(GITHUB_CONFIG.donateUrl) returning 404 (file/repo moved), 403 (GitHub rate limit or private repo), or 5xx; also a non-JSON endpoint would fail later at res.json(), but the thrown error here is specifically non-ok status.","commonSituations":"Misconfigured GITHUB_CONFIG owner/repo after renaming; rate-limited by GitHub due to no-store refetching every modal open; offline/proxied environments; donate JSON file deleted from the repo.","solutions":["Verify donateUrl resolves in a browser; correct GITHUB_CONFIG owner/repo/path if 404","If 403, back off the rate limit or reduce no-store refetch frequency / cache the result","Check connectivity to raw.githubusercontent.com (proxy/firewall)","Ship a graceful fallback UI (static donate addresses) when fetch fails"],"exampleFix":"// before\nif (!res.ok) throw new Error(`HTTP ${res.status}`);\n// after\nif (!res.ok) throw new Error(`Donation info unavailable (HTTP ${res.status})`);","handlingStrategy":"fallback","validationCode":"const res = await fetch(GITHUB_CONFIG.donateUrl, { method: \"HEAD\", cache: \"no-store\" });\nif (!res.ok) useStaticDonateFallback();","typeGuard":"function isFetchOk(res) { return Boolean(res && res.ok); }","tryCatchPattern":"try {\n  const res = await fetch(GITHUB_CONFIG.donateUrl, { cache: \"no-store\" });\n  if (!res.ok) throw new Error(`HTTP ${res.status}`);\n  setData(await res.json());\n} catch (err) {\n  setError(err.message || \"Failed to load\");\n  setData(FALLBACK_DONATE_DATA); // static addresses\n}","preventionTips":["Bundle a static fallback donate payload in the app","Soften aggressive no-store refetching to avoid GitHub rate limits","Pin donateUrl to a stable ref/commit","Alert on 404s after repo renames by checking the URL in CI"],"tags":["http","fetch","github","ui"],"backgroundTag":"http-request-failed","analyzedSha":"90b52e06ffd666b7929554211474d01588f6b1f8","analyzedAt":"2026-08-30T21:05:45.952Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}