0
# TransitionMotion Component
1
2
The TransitionMotion component handles animations for mounting and unmounting elements with full lifecycle control. It's the most advanced component in React Motion, perfect for dynamic lists, modal animations, and any scenario where elements appear and disappear with custom transition effects.
3
4
## Capabilities
5
6
### TransitionMotion Component
7
8
Manages animations for dynamically mounting and unmounting elements with customizable enter/leave transitions.
9
10
```javascript { .api }
11
/**
12
* Advanced component for animating mounting and unmounting elements
13
* Provides full lifecycle control with willEnter, willLeave, and didLeave hooks
14
*/
15
class TransitionMotion extends React.Component {
16
static propTypes: {
17
/** Initial styles for default elements (optional) */
18
defaultStyles?: Array<TransitionPlainStyle>,
19
/** Target styles or function returning styles based on previous state (required) */
20
styles: Array<TransitionStyle> | (previousInterpolatedStyles: ?Array<TransitionPlainStyle>) => Array<TransitionStyle>,
21
/** Render function receiving array of interpolated styles with keys (required) */
22
children: (interpolatedStyles: Array<TransitionPlainStyle>) => ReactElement,
23
/** Function defining how elements enter (optional, defaults to stripStyle) */
24
willEnter?: WillEnter,
25
/** Function defining how elements leave (optional, defaults to immediate removal) */
26
willLeave?: WillLeave,
27
/** Callback when element has finished leaving (optional) */
28
didLeave?: DidLeave
29
}
30
}
31
```
32
33
**Usage Examples:**
34
35
```javascript
36
import React, { useState } from 'react';
37
import { TransitionMotion, spring } from 'react-motion';
38
39
// Basic list transitions
40
function AnimatedList() {
41
const [items, setItems] = useState([
42
{key: 'a', data: 'Item A'},
43
{key: 'b', data: 'Item B'},
44
{key: 'c', data: 'Item C'}
45
]);
46
47
const addItem = () => {
48
const newKey = Date.now().toString();
49
setItems([...items, {key: newKey, data: `Item ${newKey}`}]);
50
};
51
52
const removeItem = (key) => {
53
setItems(items.filter(item => item.key !== key));
54
};
55
56
return (
57
<div>
58
<button onClick={addItem}>Add Item</button>
59
60
<TransitionMotion
61
styles={items.map(item => ({
62
key: item.key,
63
data: item.data,
64
style: {
65
opacity: spring(1),
66
scale: spring(1),
67
height: spring(60)
68
}
69
}))}
70
willEnter={() => ({
71
opacity: 0,
72
scale: 0.5,
73
height: 0
74
})}
75
willLeave={() => ({
76
opacity: spring(0),
77
scale: spring(0.5),
78
height: spring(0)
79
})}
80
>
81
{interpolatedStyles => (
82
<div>
83
{interpolatedStyles.map(({key, data, style}) => (
84
<div
85
key={key}
86
style={{
87
opacity: style.opacity,
88
transform: `scale(${style.scale})`,
89
height: `${style.height}px`,
90
background: '#f8f9fa',
91
border: '1px solid #dee2e6',
92
margin: '4px 0',
93
padding: '10px',
94
overflow: 'hidden',
95
display: 'flex',
96
alignItems: 'center',
97
justifyContent: 'space-between'
98
}}
99
>
100
<span>{data}</span>
101
<button onClick={() => removeItem(key)}>
102
Remove
103
</button>
104
</div>
105
))}
106
</div>
107
)}
108
</TransitionMotion>
109
</div>
110
);
111
}
112
113
// Modal with enter/exit animations
114
function AnimatedModal() {
115
const [showModal, setShowModal] = useState(false);
116
117
return (
118
<div>
119
<button onClick={() => setShowModal(true)}>
120
Show Modal
121
</button>
122
123
<TransitionMotion
124
styles={showModal ? [{
125
key: 'modal',
126
style: {
127
opacity: spring(1),
128
scale: spring(1),
129
backdropOpacity: spring(0.5)
130
}
131
}] : []}
132
willEnter={() => ({
133
opacity: 0,
134
scale: 0.8,
135
backdropOpacity: 0
136
})}
137
willLeave={() => ({
138
opacity: spring(0),
139
scale: spring(0.8),
140
backdropOpacity: spring(0)
141
})}
142
didLeave={() => console.log('Modal fully closed')}
143
>
144
{interpolatedStyles => (
145
<div>
146
{interpolatedStyles.map(({key, style}) => (
147
<div key={key}>
148
{/* Backdrop */}
149
<div
150
style={{
151
position: 'fixed',
152
top: 0,
153
left: 0,
154
right: 0,
155
bottom: 0,
156
background: `rgba(0, 0, 0, ${style.backdropOpacity})`,
157
zIndex: 1000
158
}}
159
onClick={() => setShowModal(false)}
160
/>
161
162
{/* Modal */}
163
<div
164
style={{
165
position: 'fixed',
166
top: '50%',
167
left: '50%',
168
transform: `translate(-50%, -50%) scale(${style.scale})`,
169
opacity: style.opacity,
170
background: 'white',
171
padding: '20px',
172
borderRadius: '8px',
173
boxShadow: '0 4px 20px rgba(0,0,0,0.3)',
174
zIndex: 1001,
175
maxWidth: '400px',
176
width: '90%'
177
}}
178
>
179
<h3>Modal Title</h3>
180
<p>Modal content goes here...</p>
181
<button onClick={() => setShowModal(false)}>
182
Close
183
</button>
184
</div>
185
</div>
186
))}
187
</div>
188
)}
189
</TransitionMotion>
190
</div>
191
);
192
}
193
194
// Todo list with complex transitions
195
function TodoList() {
196
const [todos, setTodos] = useState([
197
{id: 1, text: 'Learn React Motion', completed: false},
198
{id: 2, text: 'Build awesome animations', completed: false}
199
]);
200
const [newTodo, setNewTodo] = useState('');
201
202
const addTodo = () => {
203
if (newTodo.trim()) {
204
setTodos([...todos, {
205
id: Date.now(),
206
text: newTodo,
207
completed: false
208
}]);
209
setNewTodo('');
210
}
211
};
212
213
const toggleTodo = (id) => {
214
setTodos(todos.map(todo =>
215
todo.id === id ? {...todo, completed: !todo.completed} : todo
216
));
217
};
218
219
const deleteTodo = (id) => {
220
setTodos(todos.filter(todo => todo.id !== id));
221
};
222
223
return (
224
<div style={{maxWidth: '400px', margin: '0 auto', padding: '20px'}}>
225
<div style={{marginBottom: '20px'}}>
226
<input
227
value={newTodo}
228
onChange={e => setNewTodo(e.target.value)}
229
onKeyPress={e => e.key === 'Enter' && addTodo()}
230
placeholder="Add new todo..."
231
style={{marginRight: '10px', padding: '8px'}}
232
/>
233
<button onClick={addTodo}>Add</button>
234
</div>
235
236
<TransitionMotion
237
styles={todos.map(todo => ({
238
key: todo.id.toString(),
239
data: todo,
240
style: {
241
opacity: spring(1),
242
height: spring(50),
243
x: spring(0)
244
}
245
}))}
246
willEnter={() => ({
247
opacity: 0,
248
height: 0,
249
x: -100
250
})}
251
willLeave={(styleThatLeft) => ({
252
opacity: spring(0),
253
height: spring(0),
254
x: spring(styleThatLeft.data.completed ? 100 : -100)
255
})}
256
>
257
{interpolatedStyles => (
258
<div>
259
{interpolatedStyles.map(({key, data, style}) => (
260
<div
261
key={key}
262
style={{
263
opacity: style.opacity,
264
height: `${style.height}px`,
265
transform: `translateX(${style.x}px)`,
266
background: data.completed ? '#d4edda' : '#f8f9fa',
267
border: '1px solid #dee2e6',
268
borderRadius: '4px',
269
margin: '4px 0',
270
padding: '10px',
271
overflow: 'hidden',
272
display: 'flex',
273
alignItems: 'center',
274
justifyContent: 'space-between'
275
}}
276
>
277
<div style={{display: 'flex', alignItems: 'center'}}>
278
<input
279
type="checkbox"
280
checked={data.completed}
281
onChange={() => toggleTodo(data.id)}
282
style={{marginRight: '10px'}}
283
/>
284
<span style={{
285
textDecoration: data.completed ? 'line-through' : 'none'
286
}}>
287
{data.text}
288
</span>
289
</div>
290
<button onClick={() => deleteTodo(data.id)}>
291
Delete
292
</button>
293
</div>
294
))}
295
</div>
296
)}
297
</TransitionMotion>
298
</div>
299
);
300
}
301
```
302
303
### defaultStyles Property
304
305
Optional array of initial styles for elements that should be present by default.
306
307
```javascript { .api }
308
/**
309
* Initial styles for default elements
310
* Each item must have key, data (optional), and style properties
311
*/
312
defaultStyles?: Array<TransitionPlainStyle>;
313
```
314
315
### styles Property
316
317
Target styles array or function returning styles. Can be static array or dynamic function based on previous state.
318
319
```javascript { .api }
320
/**
321
* Target styles or function returning styles based on previous state
322
* Static: Array of TransitionStyle objects
323
* Dynamic: Function receiving previous interpolated styles
324
*/
325
styles: Array<TransitionStyle> | (previousInterpolatedStyles: ?Array<TransitionPlainStyle>) => Array<TransitionStyle>;
326
```
327
328
### children Property
329
330
Render function that receives interpolated styles with keys and data for all currently active elements.
331
332
```javascript { .api }
333
/**
334
* Render function receiving array of interpolated styles with keys
335
* Called every frame with current values for all active elements
336
*/
337
children: (interpolatedStyles: Array<TransitionPlainStyle>) => ReactElement;
338
```
339
340
### willEnter Property
341
342
Function defining how new elements should enter. Returns initial style values for mounting elements.
343
344
```javascript { .api }
345
/**
346
* Function defining how elements enter
347
* @param styleThatEntered - The TransitionStyle for the entering element
348
* @returns PlainStyle object with initial values for animation
349
*/
350
willEnter?: (styleThatEntered: TransitionStyle) => PlainStyle;
351
```
352
353
### willLeave Property
354
355
Function defining how elements should leave. Returns target style values for unmounting elements, or null for immediate removal.
356
357
```javascript { .api }
358
/**
359
* Function defining how elements leave
360
* @param styleThatLeft - The TransitionStyle for the leaving element
361
* @returns Style object for exit animation, or null for immediate removal
362
*/
363
willLeave?: (styleThatLeft: TransitionStyle) => ?Style;
364
```
365
366
### didLeave Property
367
368
Optional callback fired when an element has completely finished its leave animation and been removed.
369
370
```javascript { .api }
371
/**
372
* Callback fired when element has finished leaving
373
* @param styleThatLeft - Object with key and data of the left element
374
*/
375
didLeave?: (styleThatLeft: { key: string, data?: any }) => void;
376
```
377
378
## Core Types
379
380
### TransitionStyle
381
382
Object describing a transitioning element with unique key, optional data, and target style.
383
384
```javascript { .api }
385
interface TransitionStyle {
386
/** Unique identifier for tracking element across renders */
387
key: string;
388
/** Optional data to carry along with the element */
389
data?: any;
390
/** Target style object for this element */
391
style: Style;
392
}
393
```
394
395
### TransitionPlainStyle
396
397
Object with interpolated values passed to render function, containing current animation state.
398
399
```javascript { .api }
400
interface TransitionPlainStyle {
401
/** Unique identifier for the element */
402
key: string;
403
/** Optional data associated with element */
404
data?: any;
405
/** Current interpolated style values */
406
style: PlainStyle;
407
}
408
```
409
410
### Lifecycle Function Types
411
412
```javascript { .api }
413
/** Function type for willEnter prop */
414
type WillEnter = (styleThatEntered: TransitionStyle) => PlainStyle;
415
416
/** Function type for willLeave prop */
417
type WillLeave = (styleThatLeft: TransitionStyle) => ?Style;
418
419
/** Function type for didLeave prop */
420
type DidLeave = (styleThatLeft: { key: string, data?: any }) => void;
421
```
422
423
## Animation Lifecycle
424
425
### Element Mounting (willEnter)
426
427
1. New element appears in styles array
428
2. `willEnter` called with element's TransitionStyle
429
3. Returns initial PlainStyle values
430
4. Element animates from initial to target values
431
432
### Element Unmounting (willLeave)
433
434
1. Element removed from styles array
435
2. `willLeave` called with element's TransitionStyle
436
3. If returns null: element removed immediately
437
4. If returns Style: element animates to those values
438
5. When animation completes, `didLeave` called
439
6. Element finally removed from DOM
440
441
### Default Behaviors
442
443
```javascript
444
// Default willEnter: strips spring configs to get plain values
445
willEnter: styleThatEntered => stripStyle(styleThatEntered.style)
446
447
// Default willLeave: immediate removal
448
willLeave: () => null
449
450
// Default didLeave: no-op
451
didLeave: () => {}
452
```
453
454
## Common Patterns
455
456
### Slide In/Out List
457
458
```javascript
459
<TransitionMotion
460
styles={items.map(item => ({
461
key: item.id,
462
data: item,
463
style: {x: spring(0), opacity: spring(1)}
464
}))}
465
willEnter={() => ({x: -100, opacity: 0})}
466
willLeave={() => ({x: spring(100), opacity: spring(0)})}
467
>
468
{styles => (
469
<div>
470
{styles.map(({key, data, style}) => (
471
<div
472
key={key}
473
style={{
474
transform: `translateX(${style.x}px)`,
475
opacity: style.opacity
476
}}
477
>
478
{data.text}
479
</div>
480
))}
481
</div>
482
)}
483
</TransitionMotion>
484
```
485
486
### Scale and Fade
487
488
```javascript
489
willEnter={() => ({scale: 0, opacity: 0})}
490
willLeave={() => ({
491
scale: spring(0, {stiffness: 300}),
492
opacity: spring(0)
493
})}
494
```
495
496
### Directional Exit Based on Data
497
498
```javascript
499
willLeave={(styleThatLeft) => ({
500
x: spring(styleThatLeft.data.direction === 'left' ? -200 : 200),
501
opacity: spring(0)
502
})}
503
```
504
505
### Conditional Enter/Leave Styles
506
507
```javascript
508
willEnter={(entering) => ({
509
opacity: 0,
510
scale: entering.data.type === 'important' ? 1.2 : 0.8
511
})}
512
513
willLeave={(leaving) => ({
514
opacity: spring(0),
515
y: spring(leaving.data.deleted ? 50 : -50)
516
})}
517
```
518
519
## Performance Considerations
520
521
- Each element maintains separate animation state
522
- Key stability is crucial for proper tracking
523
- Large lists may benefit from virtualization
524
- Complex willEnter/willLeave functions run frequently during transitions
525
- Consider using React.memo for children components when data doesn't change frequently