fix(music): setTimeout 정리 + useCallback 폴링 deps

- TrendsTab: useRef로 타이머 ID 추적 후 언마운트 시 clearTimeout 호출 (stale setState 방지)
- VideoProjectsTab: loadProjects를 useCallback으로 감싸고 폴링 useEffect deps에 추가

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-01 15:13:04 +09:00
parent 08981a292a
commit 8a7b5e8a38
2 changed files with 19 additions and 7 deletions

View File

@@ -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);
});
};

View File

@@ -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 =>