perf(schedule): 카드 onClick 안정화로 React.memo 복구
카드가 onClick(schedule)을 호출하도록 변경(기존 호출부는 인자 무시라 호환), 페이지는 useCallback 안정 핸들러를 전달. 매 렌더 새 인라인 함수로 memo가 깨져 필터/스크롤마다 전체 카드가 리렌더되던 문제 해결. 검색 결과 가상화 카드는 범위에서 제외(이미 최적화됨). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
067618d792
commit
1b330872f5
8 changed files with 23 additions and 18 deletions
|
|
@ -67,7 +67,7 @@ const BirthdayCard = memo(function BirthdayCard({ schedule, showYear = false, de
|
|||
initial={{ opacity: 0, x: -10 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{ delay, type: 'spring', stiffness: 300, damping: 30 }}
|
||||
onClick={onClick}
|
||||
onClick={() => onClick?.(schedule)}
|
||||
className="cursor-pointer"
|
||||
>
|
||||
{CardContent}
|
||||
|
|
@ -76,7 +76,7 @@ const BirthdayCard = memo(function BirthdayCard({ schedule, showYear = false, de
|
|||
}
|
||||
|
||||
return (
|
||||
<div onClick={onClick} className="cursor-pointer">
|
||||
<div onClick={() => onClick?.(schedule)} className="cursor-pointer">
|
||||
{CardContent}
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ const ScheduleListCard = memo(function ScheduleListCard({
|
|||
initial={{ opacity: 0, x: -10 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{ delay, type: 'spring', stiffness: 300, damping: 30 }}
|
||||
onClick={onClick}
|
||||
onClick={() => onClick?.(schedule)}
|
||||
className={`cursor-pointer ${className}`}
|
||||
>
|
||||
{/* 카드 본체 (플랫 테두리) */}
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ const UndatedScheduleListCard = memo(function UndatedScheduleListCard({
|
|||
initial={{ opacity: 0, x: -10 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{ delay, type: 'spring', stiffness: 300, damping: 30 }}
|
||||
onClick={onClick}
|
||||
onClick={() => onClick?.(schedule)}
|
||||
className={`cursor-pointer ${className}`}
|
||||
>
|
||||
{/* 카드 본체 (점선 테두리) */}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ const BirthdayCard = memo(function BirthdayCard({ schedule, showYear = false, on
|
|||
|
||||
return (
|
||||
<div
|
||||
onClick={onClick}
|
||||
onClick={() => onClick?.(schedule)}
|
||||
className="relative overflow-hidden bg-gradient-to-r from-pink-400 via-purple-400 to-indigo-400 rounded-2xl shadow-lg hover:shadow-xl transition-shadow cursor-pointer"
|
||||
>
|
||||
{/* 배경 장식 */}
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ const ScheduleCard = memo(function ScheduleCard({ schedule, onClick, className =
|
|||
|
||||
return (
|
||||
<div
|
||||
onClick={onClick}
|
||||
onClick={() => onClick?.(schedule)}
|
||||
className={`flex items-stretch bg-white rounded-2xl shadow-sm hover:shadow-md transition-shadow overflow-hidden cursor-pointer ${className}`}
|
||||
>
|
||||
{/* 날짜 영역 */}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ const UndatedScheduleCard = memo(function UndatedScheduleCard({ schedule, onClic
|
|||
|
||||
return (
|
||||
<div
|
||||
onClick={onClick}
|
||||
onClick={() => onClick?.(schedule)}
|
||||
className={`flex items-stretch bg-white rounded-2xl border-2 border-dashed border-gray-300 hover:border-gray-400 transition-colors overflow-hidden cursor-pointer ${className}`}
|
||||
>
|
||||
{/* 월 영역 (연한 카테고리 색) */}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { useState, useEffect, useMemo, useRef } from 'react';
|
||||
import { useState, useEffect, useMemo, useRef, useCallback } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
import { ChevronLeft, ChevronRight, ChevronDown, Search, X, Calendar } from 'lucide-react';
|
||||
|
|
@ -44,6 +44,11 @@ function MobileSchedule() {
|
|||
);
|
||||
const setSelectedDate = (date) => setStoredSelectedDate(date);
|
||||
|
||||
// 카드 클릭 핸들러 (안정적 참조로 카드 React.memo 유지)
|
||||
const handleCardClick = useCallback((schedule) => {
|
||||
navigate(`/schedule/${schedule.id}`);
|
||||
}, [navigate]);
|
||||
|
||||
const [isSearchMode, setIsSearchMode] = useState(false);
|
||||
const [searchInput, setSearchInput] = useState('');
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
|
|
@ -888,7 +893,7 @@ function MobileSchedule() {
|
|||
key={schedule.id}
|
||||
schedule={schedule}
|
||||
delay={index * 0.05}
|
||||
onClick={() => navigate(`/schedule/${schedule.id}`)}
|
||||
onClick={handleCardClick}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
@ -908,7 +913,7 @@ function MobileSchedule() {
|
|||
key={schedule.id}
|
||||
schedule={schedule}
|
||||
delay={index * 0.05}
|
||||
onClick={() => navigate(`/schedule/${schedule.id}`)}
|
||||
onClick={handleCardClick}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
|
@ -926,7 +931,7 @@ function MobileSchedule() {
|
|||
key={`undated-${schedule.id}`}
|
||||
schedule={schedule}
|
||||
delay={(selectedDateSchedules.length + index) * 0.05}
|
||||
onClick={() => navigate(`/schedule/${schedule.id}`)}
|
||||
onClick={handleCardClick}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -292,7 +292,7 @@ function PCSchedule() {
|
|||
});
|
||||
|
||||
// 일정 클릭 핸들러
|
||||
const handleScheduleClick = (schedule) => {
|
||||
const handleScheduleClick = useCallback((schedule) => {
|
||||
// 생일, 데뷔, 주년 등 특수 일정
|
||||
if (schedule.is_birthday || schedule.is_debut || schedule.is_anniversary) {
|
||||
navigate(`/schedule/${schedule.id}`);
|
||||
|
|
@ -309,7 +309,7 @@ function PCSchedule() {
|
|||
} else {
|
||||
navigate(`/schedule/${schedule.id}`);
|
||||
}
|
||||
};
|
||||
}, [navigate]);
|
||||
|
||||
// 카테고리 토글
|
||||
const toggleCategory = (categoryId) => {
|
||||
|
|
@ -586,11 +586,11 @@ function PCSchedule() {
|
|||
>
|
||||
<div className={virtualItem.index < filteredSchedules.length - 1 ? 'pb-4' : ''}>
|
||||
{schedule.is_birthday ? (
|
||||
<BirthdayCard schedule={schedule} showYear onClick={() => handleScheduleClick(schedule)} />
|
||||
<BirthdayCard schedule={schedule} showYear onClick={handleScheduleClick} />
|
||||
) : schedule.is_debut || schedule.is_anniversary ? (
|
||||
<DebutCard schedule={schedule} showYear />
|
||||
) : (
|
||||
<ScheduleCard schedule={schedule} showYear onClick={() => handleScheduleClick(schedule)} />
|
||||
<ScheduleCard schedule={schedule} showYear onClick={handleScheduleClick} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -618,11 +618,11 @@ function PCSchedule() {
|
|||
transition={{ delay: Math.min(index, 10) * 0.03 }}
|
||||
>
|
||||
{schedule.is_birthday ? (
|
||||
<BirthdayCard schedule={schedule} onClick={() => handleScheduleClick(schedule)} />
|
||||
<BirthdayCard schedule={schedule} onClick={handleScheduleClick} />
|
||||
) : schedule.is_debut || schedule.is_anniversary ? (
|
||||
<DebutCard schedule={schedule} />
|
||||
) : (
|
||||
<ScheduleCard schedule={schedule} onClick={() => handleScheduleClick(schedule)} />
|
||||
<ScheduleCard schedule={schedule} onClick={handleScheduleClick} />
|
||||
)}
|
||||
</motion.div>
|
||||
))}
|
||||
|
|
@ -642,7 +642,7 @@ function PCSchedule() {
|
|||
animate={{ opacity: 1 }}
|
||||
transition={{ delay: Math.min(filteredSchedules.length + index, 10) * 0.03 }}
|
||||
>
|
||||
<UndatedScheduleCard schedule={schedule} onClick={() => handleScheduleClick(schedule)} />
|
||||
<UndatedScheduleCard schedule={schedule} onClick={handleScheduleClick} />
|
||||
</motion.div>
|
||||
))}
|
||||
</>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue