A react component toolset for managing animations
—
The TransitionGroup component manages a set of transition components in a list, automatically coordinating the mounting and unmounting of items with proper transition timing.
Manages transitions for dynamic lists of components, handling enter and exit animations for list items.
/**
* Manages a set of transition components in a list with automatic coordination
* @param props - TransitionGroup props
* @returns JSX element containing managed transition children
*/
function TransitionGroup({
component,
children,
appear,
enter,
exit,
childFactory,
...otherProps
}): JSX.Element;
interface TransitionGroupProps {
/** Component to render as container (default: 'div', use null for no wrapper) */
component?: React.ComponentType<any> | string | null;
/** Set of Transition or CSSTransition components to manage */
children?: React.ReactNode;
/** Enable/disable appear animations for all children */
appear?: boolean;
/** Enable/disable enter animations for all children */
enter?: boolean;
/** Enable/disable exit animations for all children */
exit?: boolean;
/** Function to wrap every child, including exiting ones */
childFactory?: (child: React.ReactElement) => React.ReactElement;
}Usage Examples:
import React, { useState } from 'react';
import { TransitionGroup, CSSTransition } from 'react-transition-group';
import './list.css';
function TodoList() {
const [items, setItems] = useState([
{ id: 1, text: 'Buy milk' },
{ id: 2, text: 'Walk dog' },
{ id: 3, text: 'Write code' }
]);
const addItem = () => {
const newItem = {
id: Date.now(),
text: `Item ${items.length + 1}`
};
setItems([...items, newItem]);
};
const removeItem = (id) => {
setItems(items.filter(item => item.id !== id));
};
return (
<div>
<TransitionGroup component="ul" className="todo-list">
{items.map(item => (
<CSSTransition
key={item.id}
timeout={300}
classNames="item"
>
<li onClick={() => removeItem(item.id)}>
{item.text}
</li>
</CSSTransition>
))}
</TransitionGroup>
<button onClick={addItem}>Add Item</button>
</div>
);
}
// Without wrapper element
function NoWrapperGroup({ items }) {
return (
<TransitionGroup component={null}>
{items.map(item => (
<CSSTransition
key={item.id}
timeout={200}
classNames="fade"
>
<div className="item">{item.text}</div>
</CSSTransition>
))}
</TransitionGroup>
);
}The childFactory prop allows modification of children, including those that are exiting:
/**
* Child factory function for modifying transition children
* @param child - React element being managed by TransitionGroup
* @returns Modified React element
*/
type ChildFactory = (child: React.ReactElement) => React.ReactElement;Usage Example:
function DynamicTransitionGroup({ items, fastExit }) {
const childFactory = (child) => {
// Modify exiting children to use faster animation
return React.cloneElement(child, {
timeout: fastExit ? 100 : 300,
classNames: fastExit ? 'fast-exit' : 'normal'
});
};
return (
<TransitionGroup childFactory={childFactory}>
{items.map(item => (
<CSSTransition
key={item.id}
timeout={300}
classNames="normal"
>
<div>{item.text}</div>
</CSSTransition>
))}
</TransitionGroup>
);
}Common patterns for animating lists with TransitionGroup:
Staggered Animations:
function StaggeredList({ items }) {
return (
<TransitionGroup component="div" className="staggered-list">
{items.map((item, index) => (
<CSSTransition
key={item.id}
timeout={300}
classNames="stagger"
style={{ animationDelay: `${index * 50}ms` }}
>
<div className="list-item">
{item.text}
</div>
</CSSTransition>
))}
</TransitionGroup>
);
}Complex List Operations:
function AdvancedList() {
const [items, setItems] = useState([]);
const handleReorder = (fromIndex, toIndex) => {
const newItems = [...items];
const [removed] = newItems.splice(fromIndex, 1);
newItems.splice(toIndex, 0, removed);
setItems(newItems);
};
return (
<TransitionGroup component="div">
{items.map(item => (
<CSSTransition
key={item.id}
timeout={{ enter: 300, exit: 200 }}
classNames="list-item"
>
<div
className="draggable-item"
onDragEnd={(e) => handleReorder(...)}
>
{item.text}
</div>
</CSSTransition>
))}
</TransitionGroup>
);
}TransitionGroup automatically:
in prop to true for entering items and false for exiting itemsAll children of TransitionGroup must have unique key props:
// ✅ Correct - each child has unique key
<TransitionGroup>
{items.map(item => (
<CSSTransition key={item.id} timeout={200} classNames="fade">
<div>{item.text}</div>
</CSSTransition>
))}
</TransitionGroup>
// ❌ Incorrect - missing or duplicate keys will cause issues
<TransitionGroup>
{items.map(item => (
<CSSTransition timeout={200} classNames="fade">
<div>{item.text}</div>
</CSSTransition>
))}
</TransitionGroup>/* Slide in from right, slide out to left */
.item-enter {
transform: translateX(100%);
opacity: 0;
}
.item-enter-active {
transform: translateX(0);
opacity: 1;
transition: all 300ms ease-out;
}
.item-exit {
transform: translateX(0);
opacity: 1;
}
.item-exit-active {
transform: translateX(-100%);
opacity: 0;
transition: all 200ms ease-in;
}
/* Scale and fade */
.scale-enter {
transform: scale(0.8);
opacity: 0;
}
.scale-enter-active {
transform: scale(1);
opacity: 1;
transition: all 250ms cubic-bezier(0.175, 0.885, 0.32, 1.275);
}
.scale-exit {
transform: scale(1);
opacity: 1;
}
.scale-exit-active {
transform: scale(0.8);
opacity: 0;
transition: all 150ms ease-in;
}Install with Tessl CLI
npx tessl i tessl/npm-react-transition-group