fix: reduce store rerenders and cleanup timers
This commit is contained in:
@@ -397,10 +397,12 @@ function CanvasPage({
|
||||
const suppressNextPaneClickRef = useRef(false);
|
||||
const canvasAutoSaveTimerRef = useRef<number | null>(null);
|
||||
const canvasAutoSaveIdleHandleRef = useRef<number | null>(null);
|
||||
const canvasAutoSaveRetryTimerRef = useRef<number | null>(null);
|
||||
const canvasAutoSaveInFlightRef = useRef(false);
|
||||
const canvasAutoSavePendingRef = useRef(false);
|
||||
const lastAutoSavedWorkflowFingerprintRef = useRef("");
|
||||
const canvasAutoSaveHydrationRef = useRef(true);
|
||||
const textNodeMentionFocusTimerRef = useRef<number | null>(null);
|
||||
const textNodeIdRef = useRef(9);
|
||||
const imageNodeIdRef = useRef(1);
|
||||
const videoNodeIdRef = useRef(1);
|
||||
@@ -519,7 +521,11 @@ function CanvasPage({
|
||||
else if (kind === "video") updateVideoNodePrompt(nodeId, nextValue);
|
||||
else updateTextNodePrompt(nodeId, nextValue);
|
||||
closeTextNodeMention(nodeId);
|
||||
setTimeout(() => {
|
||||
if (textNodeMentionFocusTimerRef.current !== null) {
|
||||
window.clearTimeout(textNodeMentionFocusTimerRef.current);
|
||||
}
|
||||
textNodeMentionFocusTimerRef.current = window.setTimeout(() => {
|
||||
textNodeMentionFocusTimerRef.current = null;
|
||||
if (textarea) {
|
||||
textarea.focus();
|
||||
textarea.setSelectionRange(nextCaret, nextCaret);
|
||||
@@ -555,6 +561,18 @@ function CanvasPage({
|
||||
const [autoSaveStatus, setAutoSaveStatus] = useState<"saved" | "saving" | "error" | "idle">("idle");
|
||||
const autoSaveStatusTimerRef = useRef<number | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (canvasAutoSaveTimerRef.current !== null) window.clearTimeout(canvasAutoSaveTimerRef.current);
|
||||
if (canvasAutoSaveRetryTimerRef.current !== null) window.clearTimeout(canvasAutoSaveRetryTimerRef.current);
|
||||
if (autoSaveStatusTimerRef.current !== null) window.clearTimeout(autoSaveStatusTimerRef.current);
|
||||
if (textNodeMentionFocusTimerRef.current !== null) window.clearTimeout(textNodeMentionFocusTimerRef.current);
|
||||
if (canvasAutoSaveIdleHandleRef.current !== null && "cancelIdleCallback" in window) {
|
||||
window.cancelIdleCallback(canvasAutoSaveIdleHandleRef.current);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
// Save immediately when user leaves page or switches tab (placed after runCanvasAutoSave definition)
|
||||
// — see useEffect below near runCanvasAutoSave
|
||||
|
||||
@@ -3159,7 +3177,13 @@ function CanvasPage({
|
||||
canvasAutoSaveInFlightRef.current = false;
|
||||
if (canvasAutoSavePendingRef.current) {
|
||||
canvasAutoSavePendingRef.current = false;
|
||||
window.setTimeout(() => void runCanvasAutoSave(), canvasAutoSaveIdleTimeoutMs);
|
||||
if (canvasAutoSaveRetryTimerRef.current !== null) {
|
||||
window.clearTimeout(canvasAutoSaveRetryTimerRef.current);
|
||||
}
|
||||
canvasAutoSaveRetryTimerRef.current = window.setTimeout(() => {
|
||||
canvasAutoSaveRetryTimerRef.current = null;
|
||||
void runCanvasAutoSave();
|
||||
}, canvasAutoSaveIdleTimeoutMs);
|
||||
}
|
||||
}
|
||||
}, [
|
||||
@@ -3228,7 +3252,13 @@ function CanvasPage({
|
||||
);
|
||||
return;
|
||||
}
|
||||
window.setTimeout(() => void runCanvasAutoSave(), canvasAutoSaveIdleTimeoutMs);
|
||||
if (canvasAutoSaveRetryTimerRef.current !== null) {
|
||||
window.clearTimeout(canvasAutoSaveRetryTimerRef.current);
|
||||
}
|
||||
canvasAutoSaveRetryTimerRef.current = window.setTimeout(() => {
|
||||
canvasAutoSaveRetryTimerRef.current = null;
|
||||
void runCanvasAutoSave();
|
||||
}, canvasAutoSaveIdleTimeoutMs);
|
||||
}, canvasAutoSaveDebounceMs);
|
||||
|
||||
return () => {
|
||||
@@ -3240,6 +3270,10 @@ function CanvasPage({
|
||||
window.cancelIdleCallback(canvasAutoSaveIdleHandleRef.current);
|
||||
canvasAutoSaveIdleHandleRef.current = null;
|
||||
}
|
||||
if (canvasAutoSaveRetryTimerRef.current !== null) {
|
||||
window.clearTimeout(canvasAutoSaveRetryTimerRef.current);
|
||||
canvasAutoSaveRetryTimerRef.current = null;
|
||||
}
|
||||
};
|
||||
}, [
|
||||
isAuthenticated,
|
||||
|
||||
@@ -1098,6 +1098,8 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
|
||||
};
|
||||
|
||||
const clearCloneSetCountHold = () => {
|
||||
window.removeEventListener("pointerup", clearCloneSetCountHold);
|
||||
window.removeEventListener("pointercancel", clearCloneSetCountHold);
|
||||
if (countHoldTimeoutRef.current !== null) {
|
||||
window.clearTimeout(countHoldTimeoutRef.current);
|
||||
countHoldTimeoutRef.current = null;
|
||||
|
||||
@@ -121,6 +121,7 @@ export default function EcommerceVideoWorkspace({
|
||||
const [previewMedia, setPreviewMedia] = useState<{ url: string; type: "image" | "video" } | null>(null);
|
||||
const abortControllerRef = useRef<AbortController | null>(null);
|
||||
const renderAbortRef = useRef({ current: false });
|
||||
const actionNoticeTimerRef = useRef<number | null>(null);
|
||||
const setView = useAppStore((s) => s.setView);
|
||||
const keepaliveRestoredFingerprintRef = useRef<string | null>(null);
|
||||
const keepalivePollingStartedRef = useRef(false);
|
||||
@@ -276,9 +277,23 @@ export default function EcommerceVideoWorkspace({
|
||||
// Note: keep-alive is NOT cleared on completion — results persist across page switches.
|
||||
// Only cleared when user explicitly starts a new plan via handlePlan.
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (actionNoticeTimerRef.current !== null) {
|
||||
window.clearTimeout(actionNoticeTimerRef.current);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
const showNotice = (msg: string) => {
|
||||
setActionNotice(msg);
|
||||
setTimeout(() => setActionNotice(null), 3000);
|
||||
if (actionNoticeTimerRef.current !== null) {
|
||||
window.clearTimeout(actionNoticeTimerRef.current);
|
||||
}
|
||||
actionNoticeTimerRef.current = window.setTimeout(() => {
|
||||
actionNoticeTimerRef.current = null;
|
||||
setActionNotice(null);
|
||||
}, 3000);
|
||||
};
|
||||
|
||||
const handleDownload = async (url: string) => {
|
||||
|
||||
@@ -50,6 +50,12 @@ function ScriptReviewShowcase() {
|
||||
const scoreRef = useRef<HTMLSpanElement>(null);
|
||||
const barRefs = useRef<(HTMLDivElement | null)[]>([]);
|
||||
const scoreValRefs = useRef<(HTMLSpanElement | null)[]>([]);
|
||||
const animationTimersRef = useRef<ReturnType<typeof setTimeout>[]>([]);
|
||||
|
||||
const clearAnimationTimers = () => {
|
||||
animationTimersRef.current.forEach((timer) => clearTimeout(timer));
|
||||
animationTimersRef.current = [];
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const el = document.getElementById("script-review-showcase");
|
||||
@@ -69,18 +75,23 @@ function ScriptReviewShowcase() {
|
||||
|
||||
useEffect(() => {
|
||||
if (!animated) return;
|
||||
const timer = setTimeout(() => {
|
||||
clearAnimationTimers();
|
||||
const scheduleAnimation = (callback: () => void, delay: number) => {
|
||||
const timer = setTimeout(callback, delay);
|
||||
animationTimersRef.current.push(timer);
|
||||
};
|
||||
scheduleAnimation(() => {
|
||||
animateNumber(scoreRef.current, 77, 1400);
|
||||
barRefs.current.forEach((bar, i) => {
|
||||
if (!bar) return;
|
||||
const pct = parseFloat(bar.dataset.pct ?? "0");
|
||||
setTimeout(() => { bar.style.height = `${pct}%`; }, i * 100 + 400);
|
||||
scheduleAnimation(() => { bar.style.height = `${pct}%`; }, i * 100 + 400);
|
||||
});
|
||||
scoreValRefs.current.forEach((el, i) => {
|
||||
setTimeout(() => animateNumber(el, parseInt(el?.dataset.target ?? "0"), 800), i * 100 + 400);
|
||||
scheduleAnimation(() => animateNumber(el, parseInt(el?.dataset.target ?? "0"), 800), i * 100 + 400);
|
||||
});
|
||||
}, 500);
|
||||
return () => clearTimeout(timer);
|
||||
return clearAnimationTimers;
|
||||
}, [animated]);
|
||||
|
||||
return (
|
||||
|
||||
Reference in New Issue
Block a user