# Examples ## Isolate a ticking timer from a long list **Scenario:** A message list re-renders every second because a timer (`elapsedMs`) lives in the parent component. This causes visible jank on large lists. **Goal:** Keep UI identical but limit re-renders to the timer area. **Before (problematic pattern):** ```tsx function Messages({ items, isThinking, processingStartedAt }) { const [elapsedMs, setElapsedMs] = useState(0); useEffect(() => { if (!isThinking || !processingStartedAt) { setElapsedMs(0); return; } setElapsedMs(Date.now() - processingStartedAt); const interval = window.setInterval(() => { setElapsedMs(Date.now() - processingStartedAt); }, 1000); return () => window.clearInterval(interval); }, [isThinking, processingStartedAt]); return (
{items.map((item) => ( ))}
{formatDurationMs(elapsedMs)}
); } ``` **After (isolated ticking state):** ```tsx type WorkingIndicatorProps = { isThinking: boolean; processingStartedAt?: number | null; }; const WorkingIndicator = memo(function WorkingIndicator({ isThinking, processingStartedAt = null, }: WorkingIndicatorProps) { const [elapsedMs, setElapsedMs] = useState(0); useEffect(() => { if (!isThinking || !processingStartedAt) { setElapsedMs(0); return; } setElapsedMs(Date.now() - processingStartedAt); const interval = window.setInterval(() => { setElapsedMs(Date.now() - processingStartedAt); }, 1000); return () => window.clearInterval(interval); }, [isThinking, processingStartedAt]); return
{formatDurationMs(elapsedMs)}
; }); function Messages({ items, isThinking, processingStartedAt }) { return (
{items.map((item) => ( ))}
); } ``` **Why it helps:** Only the `WorkingIndicator` subtree re-renders every second. The list remains stable unless its props change. **Optional follow-ups:** - Wrap `MessageRow` in `memo` if props are stable. - Use `useCallback` for handlers passed to rows to avoid re-render churn. - Consider list virtualization if the list is very large.