From 8a7b5e8a389686f958977a9e87010c7f6ad4eda0 Mon Sep 17 00:00:00 2001 From: gahusb Date: Fri, 1 May 2026 15:13:04 +0900 Subject: [PATCH] =?UTF-8?q?fix(music):=20setTimeout=20=EC=A0=95=EB=A6=AC?= =?UTF-8?q?=20+=20useCallback=20=ED=8F=B4=EB=A7=81=20deps?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - TrendsTab: useRef로 타이머 ID 추적 후 언마운트 시 clearTimeout 호출 (stale setState 방지) - VideoProjectsTab: loadProjects를 useCallback으로 감싸고 폴링 useEffect deps에 추가 Co-Authored-By: Claude Sonnet 4.6 --- src/pages/music/components/TrendsTab.jsx | 18 +++++++++++++++--- .../music/components/VideoProjectsTab.jsx | 8 ++++---- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/src/pages/music/components/TrendsTab.jsx b/src/pages/music/components/TrendsTab.jsx index 07cb7c0..c53dbfd 100644 --- a/src/pages/music/components/TrendsTab.jsx +++ b/src/pages/music/components/TrendsTab.jsx @@ -1,5 +1,5 @@ // src/pages/music/components/TrendsTab.jsx -import { useState, useEffect } from 'react'; +import { useState, useEffect, useRef } from 'react'; import { getLatestTrendReport, getTrendReports, getMarketSuggestions, triggerYoutubeResearch, @@ -28,6 +28,9 @@ export default function TrendsTab() { const [loading, setLoading] = useState(true); const [researchMsg, setResearchMsg] = useState(''); + const researchTimerRef = useRef(null); + const copyTimerRef = useRef(null); + const loadAll = async () => { setLoading(true); try { @@ -48,12 +51,20 @@ export default function TrendsTab() { useEffect(() => { loadAll(); }, []); + useEffect(() => { + return () => { + clearTimeout(researchTimerRef.current); + clearTimeout(copyTimerRef.current); + }; + }, []); + const handleResearch = async () => { setResearching(true); try { await triggerYoutubeResearch(); setResearchMsg('수집이 시작되었습니다. 잠시 후 새로고침하세요.'); - setTimeout(() => setResearchMsg(''), 4000); + clearTimeout(researchTimerRef.current); + researchTimerRef.current = setTimeout(() => setResearchMsg(''), 4000); } catch (e) { console.error('triggerYoutubeResearch:', e); } finally { @@ -64,7 +75,8 @@ export default function TrendsTab() { const handleCopy = (text, idx) => { navigator.clipboard.writeText(text).then(() => { setCopiedIdx(idx); - setTimeout(() => setCopiedIdx(null), 2000); + clearTimeout(copyTimerRef.current); + copyTimerRef.current = setTimeout(() => setCopiedIdx(null), 2000); }); }; diff --git a/src/pages/music/components/VideoProjectsTab.jsx b/src/pages/music/components/VideoProjectsTab.jsx index 2a87a95..61491c1 100644 --- a/src/pages/music/components/VideoProjectsTab.jsx +++ b/src/pages/music/components/VideoProjectsTab.jsx @@ -1,5 +1,5 @@ // src/pages/music/components/VideoProjectsTab.jsx -import { useState, useEffect, useRef } from 'react'; +import { useState, useEffect, useRef, useCallback } from 'react'; import { createVideoProject, getVideoProjects, renderVideoProject, exportVideoProject, deleteVideoProject, @@ -26,14 +26,14 @@ export default function VideoProjectsTab({ library, initialTrackId, onClearIniti } }, [initialTrackId]); - const loadProjects = async () => { + const loadProjects = useCallback(async () => { try { const data = await getVideoProjects(); setProjects(Array.isArray(data) ? data : data.projects ?? []); } catch (e) { console.error('getVideoProjects:', e); } - }; + }, []); useEffect(() => { loadProjects(); }, []); @@ -49,7 +49,7 @@ export default function VideoProjectsTab({ library, initialTrackId, onClearIniti return () => { if (pollRef.current) { clearInterval(pollRef.current); pollRef.current = null; } }; - }, [projects]); + }, [projects, loadProjects]); const toggleCountry = (c) => { setCountries(prev =>