-
-
Notifications
You must be signed in to change notification settings - Fork 953
Fix(webapp): Dynamically load spline on 404 on page #2834
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
78ddbe8
1835117
fdfbb07
42b385b
14ccf8d
c954411
8997f7c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,75 @@ | ||
| import { motion } from "framer-motion"; | ||
| import { useEffect, useState } from "react"; | ||
|
|
||
| declare global { | ||
| namespace JSX { | ||
| interface IntrinsicElements { | ||
| "spline-viewer": React.DetailedHTMLProps< | ||
| React.HTMLAttributes<HTMLElement> & { | ||
| url?: string; | ||
| "loading-anim-type"?: string; | ||
| }, | ||
| HTMLElement | ||
| >; | ||
| } | ||
| } | ||
|
|
||
| interface Window { | ||
| __splineLoader?: Promise<void>; | ||
| } | ||
| } | ||
|
|
||
| export function TriggerRotatingLogo() { | ||
| const [isSplineReady, setIsSplineReady] = useState(false); | ||
|
|
||
| useEffect(() => { | ||
| // Already registered from a previous render | ||
| if (customElements.get("spline-viewer")) { | ||
| setIsSplineReady(true); | ||
| return; | ||
| } | ||
|
|
||
| // Another mount already started loading - share the same promise | ||
| if (window.__splineLoader) { | ||
| window.__splineLoader.then(() => setIsSplineReady(true)).catch(() => setIsSplineReady(false)); | ||
| return; | ||
| } | ||
|
|
||
| // First mount: create script and shared loader promise | ||
| const script = document.createElement("script"); | ||
| script.type = "module"; | ||
| // Version pinned; SRI hash omitted as unpkg doesn't guarantee hash stability across deploys | ||
| script.src = "https://unpkg.com/@splinetool/viewer@1.12.29/build/spline-viewer.js"; | ||
|
|
||
| window.__splineLoader = new Promise<void>((resolve, reject) => { | ||
| script.onload = () => resolve(); | ||
| script.onerror = () => reject(); | ||
| }); | ||
|
|
||
| window.__splineLoader.then(() => setIsSplineReady(true)).catch(() => setIsSplineReady(false)); | ||
|
|
||
| document.head.appendChild(script); | ||
|
|
||
| // Intentionally no cleanup: once the custom element is registered globally, | ||
| // removing the script would break re-mounts while providing no benefit | ||
| }, []); | ||
|
Comment on lines
+25
to
+55
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Race condition successfully addressed; prevent setState after unmount. The shared 🔧 Proposed fix using cleanup pattern export function TriggerRotatingLogo() {
const [isSplineReady, setIsSplineReady] = useState(false);
useEffect(() => {
+ let mounted = true;
+
// Already registered from a previous render
if (customElements.get("spline-viewer")) {
setIsSplineReady(true);
return;
}
// Another mount already started loading - share the same promise
if (window.__splineLoader) {
- window.__splineLoader.then(() => setIsSplineReady(true)).catch(() => setIsSplineReady(false));
+ window.__splineLoader
+ .then(() => mounted && setIsSplineReady(true))
+ .catch(() => mounted && setIsSplineReady(false));
return;
}
// First mount: create script and shared loader promise
const script = document.createElement("script");
script.type = "module";
// Version pinned; SRI hash omitted as unpkg doesn't guarantee hash stability across deploys
script.src = "https://unpkg.com/@splinetool/viewer@1.12.29/build/spline-viewer.js";
window.__splineLoader = new Promise<void>((resolve, reject) => {
script.onload = () => resolve();
script.onerror = () => reject();
});
- window.__splineLoader.then(() => setIsSplineReady(true)).catch(() => setIsSplineReady(false));
+ window.__splineLoader
+ .then(() => mounted && setIsSplineReady(true))
+ .catch(() => mounted && setIsSplineReady(false));
document.head.appendChild(script);
// Intentionally no cleanup: once the custom element is registered globally,
// removing the script would break re-mounts while providing no benefit
+
+ return () => {
+ mounted = false;
+ };
}, []);🤖 Prompt for AI Agents |
||
|
|
||
| if (!isSplineReady) { | ||
| return null; | ||
| } | ||
|
|
||
| return ( | ||
| <motion.div | ||
| className="pointer-events-none absolute inset-0 overflow-hidden" | ||
| initial={{ opacity: 0 }} | ||
| animate={{ opacity: 1 }} | ||
| transition={{ delay: 0.5, duration: 2, ease: "easeOut" }} | ||
| > | ||
| <spline-viewer | ||
| loading-anim-type="spinner-small-light" | ||
| url="https://prod.spline.design/wRly8TZN-e0Twb8W/scene.splinecode" | ||
| style={{ width: "100%", height: "100%" }} | ||
| /> | ||
| </motion.div> | ||
| ); | ||
| } | ||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Uh oh!
There was an error while loading. Please reload this page.