or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

index.mdmotion.mdspring-system.mdstaggered-motion.mdtransition-motion.md

transition-motion.mddocs/

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