React Transition Group
React Transition Group is a set of low-level primitive components for managing component states over time, specifically designed to facilitate animations in React applications. It enables developers to define and control lifecycle events for components entering, exiting, or remaining in the DOM. The current stable version is 4.4.5, last updated in August 2022, with a release cadence focused on stability and compatibility with React's evolving ecosystem. This library doesn't dictate specific animation libraries or CSS frameworks; instead, it provides hooks and class toggles (`CSSTransition`) that allow integration with arbitrary CSS transitions/animations or JavaScript animation libraries. Its key differentiator is providing an unopinionated, foundational API for animation patterns, contrasting with higher-level animation libraries that often bundle their own animation engines or opinions.
Common errors
-
Warning: findDOMNode is deprecated in StrictMode. findDOMNode was passed an instance of CSSTransition which is inside StrictMode. Instead, add a ref directly to the element you want to reference.
cause Using `CSSTransition` or `Transition` without explicitly passing a `nodeRef` prop in React Strict Mode or with React 18+.fixProvide a `nodeRef={React.createRef<HTMLElement>()}` prop to your `CSSTransition` or `Transition` component, and ensure the direct child component forwards this ref to the DOM element being animated. For `CSSTransition`, use the render prop pattern to access `state.nodeRef`. -
The 'classNames' prop should be a string or a plain object. The 'classNames' prop must be provided to CSSTransition.
cause Incorrectly configuring the `classNames` prop on `CSSTransition`, or forgetting to provide it altogether.fixEnsure `classNames` is either a string (e.g., `classNames="my-animation"`) which will generate `my-animation-enter`, `my-animation-exit` etc., or an object specifying custom class names for each state (e.g., `{ enter: 'my-enter', exit: 'my-exit' }`). -
TypeError: Cannot read properties of undefined (reading 'appendChild') or similar DOM manipulation errors when unmounting.
cause This can sometimes occur if `TransitionGroup` has `component={null}` and the immediate children are not handling their own DOM mounting/unmounting correctly, or if animations are trying to run on an already unmounted node.fixEnsure `TransitionGroup` is rendered with a valid DOM element as its `component` prop (e.g., `'div'`, `'ul'`). Alternatively, ensure all children of `TransitionGroup` are `CSSTransition` or `Transition` components, and that `nodeRef` is correctly implemented for each. For complex scenarios, consider using `mountOnEnter` and `unmountOnExit` to control DOM presence.
Warnings
- breaking The API for `react-transition-group` version 2 and above is not backward compatible with the original `react-addons-transition-group` (v1). Significant changes were made to the component structure and prop requirements. Code written for v1 will not work with v2+ without migration.
- gotcha Using `react-transition-group` in React Strict Mode (or with React 18+) without the `nodeRef` prop will produce `findDOMNode is deprecated in StrictMode` warnings in the console. This is because `findDOMNode` is an internal mechanism previously used by the library.
- deprecated Version 1 of `react-transition-group` is no longer actively maintained. While still available, it's recommended to upgrade to the latest stable version for bug fixes, performance improvements, and compatibility with newer React features.
- gotcha The `Transition` component provides basic lifecycle hooks and state management for animating. For applying CSS classes during transitions (e.g., `fade-enter`, `fade-enter-active`), you should use `CSSTransition`, which extends `Transition` specifically for this purpose.
Install
-
npm install react-transition-group -
yarn add react-transition-group -
pnpm add react-transition-group
Imports
- Transition
const Transition = require('react-transition-group').Transition;import { Transition } from 'react-transition-group'; - CSSTransition
import CSSTransition from 'react-transition-group/CSSTransition';
import { CSSTransition } from 'react-transition-group'; - TransitionGroup
const TransitionGroup = require('react-transition-group');import { TransitionGroup } from 'react-transition-group'; - SwitchTransition
import { SwitchTransition } from 'react-transition-group';
Quickstart
import React, { useState } from 'react';
import ReactDOM from 'react-dom/client';
import { CSSTransition, TransitionGroup } from 'react-transition-group';
// Basic CSS for demonstration (e.g., in App.css or a style tag)
/*
.item-enter {
opacity: 0;
}
.item-enter-active {
opacity: 1;
transition: opacity 500ms ease-in;
}
.item-exit {
opacity: 1;
}
.item-exit-active {
opacity: 0;
transition: opacity 500ms ease-out;
}
*/
interface TodoItemProps {
todo: string;
onRemove: () => void;
}
const TodoItem: React.FC<TodoItemProps> = ({ todo, onRemove }) => (
<div className="todo-item" onClick={onRemove}>
{todo}
</div>
);
const App: React.FC = () => {
const [todos, setTodos] = useState<string[]>(["Learn React", "Build something", "Deploy it"]);
const [newTodo, setNewTodo] = useState<string>('');
const nextId = React.useRef(todos.length);
const handleAddTodo = () => {
if (newTodo.trim() === '') return;
setTodos([...todos, newTodo.trim()]);
setNewTodo('');
nextId.current++;
};
const handleRemoveTodo = (indexToRemove: number) => {
setTodos(todos.filter((_, index) => index !== indexToRemove));
};
return (
<div>
<h1>Animated Todo List</h1>
<input
type="text"
value={newTodo}
onChange={(e) => setNewTodo(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && handleAddTodo()}
placeholder="Add a new todo"
/>
<button onClick={handleAddTodo}>Add Todo</button>
<TransitionGroup component="ul" style={{ listStyle: 'none', padding: 0 }}>
{todos.map((todo, index) => (
<CSSTransition
key={todo + index} // Using todo + index for a unique key, though real apps might use a stable ID
timeout={500}
classNames="item"
nodeRef={React.createRef<HTMLLIElement>()} // Required for Strict Mode and React 18+
>
{(state) => (
<li ref={state.nodeRef} style={{marginBottom: '5px'}}>
<TodoItem todo={todo} onRemove={() => handleRemoveTodo(index)} />
</li>
)}
</CSSTransition>
))}
</TransitionGroup>
</div>
);
};
const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement);
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);