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:
@@ -1,5 +1,5 @@
|
|||||||
// src/pages/music/components/TrendsTab.jsx
|
// src/pages/music/components/TrendsTab.jsx
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect, useRef } from 'react';
|
||||||
import {
|
import {
|
||||||
getLatestTrendReport, getTrendReports,
|
getLatestTrendReport, getTrendReports,
|
||||||
getMarketSuggestions, triggerYoutubeResearch,
|
getMarketSuggestions, triggerYoutubeResearch,
|
||||||
@@ -28,6 +28,9 @@ export default function TrendsTab() {
|
|||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [researchMsg, setResearchMsg] = useState('');
|
const [researchMsg, setResearchMsg] = useState('');
|
||||||
|
|
||||||
|
const researchTimerRef = useRef(null);
|
||||||
|
const copyTimerRef = useRef(null);
|
||||||
|
|
||||||
const loadAll = async () => {
|
const loadAll = async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
@@ -48,12 +51,20 @@ export default function TrendsTab() {
|
|||||||
|
|
||||||
useEffect(() => { loadAll(); }, []);
|
useEffect(() => { loadAll(); }, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
clearTimeout(researchTimerRef.current);
|
||||||
|
clearTimeout(copyTimerRef.current);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
const handleResearch = async () => {
|
const handleResearch = async () => {
|
||||||
setResearching(true);
|
setResearching(true);
|
||||||
try {
|
try {
|
||||||
await triggerYoutubeResearch();
|
await triggerYoutubeResearch();
|
||||||
setResearchMsg('수집이 시작되었습니다. 잠시 후 새로고침하세요.');
|
setResearchMsg('수집이 시작되었습니다. 잠시 후 새로고침하세요.');
|
||||||
setTimeout(() => setResearchMsg(''), 4000);
|
clearTimeout(researchTimerRef.current);
|
||||||
|
researchTimerRef.current = setTimeout(() => setResearchMsg(''), 4000);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('triggerYoutubeResearch:', e);
|
console.error('triggerYoutubeResearch:', e);
|
||||||
} finally {
|
} finally {
|
||||||
@@ -64,7 +75,8 @@ export default function TrendsTab() {
|
|||||||
const handleCopy = (text, idx) => {
|
const handleCopy = (text, idx) => {
|
||||||
navigator.clipboard.writeText(text).then(() => {
|
navigator.clipboard.writeText(text).then(() => {
|
||||||
setCopiedIdx(idx);
|
setCopiedIdx(idx);
|
||||||
setTimeout(() => setCopiedIdx(null), 2000);
|
clearTimeout(copyTimerRef.current);
|
||||||
|
copyTimerRef.current = setTimeout(() => setCopiedIdx(null), 2000);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// src/pages/music/components/VideoProjectsTab.jsx
|
// src/pages/music/components/VideoProjectsTab.jsx
|
||||||
import { useState, useEffect, useRef } from 'react';
|
import { useState, useEffect, useRef, useCallback } from 'react';
|
||||||
import {
|
import {
|
||||||
createVideoProject, getVideoProjects,
|
createVideoProject, getVideoProjects,
|
||||||
renderVideoProject, exportVideoProject, deleteVideoProject,
|
renderVideoProject, exportVideoProject, deleteVideoProject,
|
||||||
@@ -26,14 +26,14 @@ export default function VideoProjectsTab({ library, initialTrackId, onClearIniti
|
|||||||
}
|
}
|
||||||
}, [initialTrackId]);
|
}, [initialTrackId]);
|
||||||
|
|
||||||
const loadProjects = async () => {
|
const loadProjects = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
const data = await getVideoProjects();
|
const data = await getVideoProjects();
|
||||||
setProjects(Array.isArray(data) ? data : data.projects ?? []);
|
setProjects(Array.isArray(data) ? data : data.projects ?? []);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('getVideoProjects:', e);
|
console.error('getVideoProjects:', e);
|
||||||
}
|
}
|
||||||
};
|
}, []);
|
||||||
|
|
||||||
useEffect(() => { loadProjects(); }, []);
|
useEffect(() => { loadProjects(); }, []);
|
||||||
|
|
||||||
@@ -49,7 +49,7 @@ export default function VideoProjectsTab({ library, initialTrackId, onClearIniti
|
|||||||
return () => {
|
return () => {
|
||||||
if (pollRef.current) { clearInterval(pollRef.current); pollRef.current = null; }
|
if (pollRef.current) { clearInterval(pollRef.current); pollRef.current = null; }
|
||||||
};
|
};
|
||||||
}, [projects]);
|
}, [projects, loadProjects]);
|
||||||
|
|
||||||
const toggleCountry = (c) => {
|
const toggleCountry = (c) => {
|
||||||
setCountries(prev =>
|
setCountries(prev =>
|
||||||
|
|||||||
Reference in New Issue
Block a user