typescriptadvanced

useTransitionGroup Hook

Manages enter/exit animations for list items with staggered transitions.

typescript
import { useState, useEffect, useRef } from 'react';

interface TransitionItem<T> {
  item: T;
  state: 'entering' | 'entered' | 'exiting';
  key: string;
}

export function useTransitionGroup<T extends { id: string }>(items: T[], duration = 300) {
  const [rendered, setRendered] = useState<TransitionItem<T>[]>([]);
  const prevItems = useRef<T[]>([]);

  useEffect(() => {
    const prevIds = new Set(prevItems.current.map(i => i.id));
    const currentIds = new Set(items.map(i => i.id));

    const entering = items.filter(i => !prevIds.has(i.id))
      .map(item => ({ item, state: 'entering' as const, key: item.id }));

    const staying = items.filter(i => prevIds.has(i.id))
      .map(item => ({ item, state: 'entered' as const, key: item.id }));

    const exiting = prevItems.current.filter(i => !currentIds.has(i.id))
      .map(item => ({ item, state: 'exiting' as const, key: item.id }));

    setRendered([...staying, ...entering, ...exiting]);

    const timer = setTimeout(() => {
      setRendered(prev => prev
        .filter(i => i.state !== 'exiting')
        .map(i => ({ ...i, state: 'entered' }))
      );
    }, duration);

    prevItems.current = items;
    return () => clearTimeout(timer);
  }, [items, duration]);

  return rendered;
}

Use Cases

  • Animated list additions/removals
  • Toast notifications

Tags

Related Snippets

Similar patterns you can reuse in the same workflow.