We use cookies to enhance your experience on the site
CodeWorlds

React Transition Group - Animating Interstellar UI Journeys

Moving between different states and views in a space application should be as smooth as a spaceship flying through hyperspace. Sudden appearance and disappearance of interface elements feels more like a system failure than a well-designed user experience. In this part of the mission, we will learn about React Transition Group - a library that will help us create smooth, cosmically good transition animations in our interfaces.

What is React Transition Group?

React Transition Group is a low-level API library for component enter/exit animations. Unlike ready-made animation solutions, it does not provide predefined effects - instead, it gives developers full control over the animation process by managing CSS classes or styles at key moments in the component lifecycle.

It is like a spaceship control panel - it does not directly steer the flight, but gives you the tools for precise maneuvering.

Key React Transition Group Components

1.
Transition

The basic lower-level component that manages component transition states:

1import { Transition } from 'react-transition-group';
2import React, { useState } from 'react';
3
4// Styles for different states
5const duration = 300;
6const defaultStyle = {
7  transition: `opacity ${duration}ms ease-in-out`,
8  opacity: 0,
9}
10
11const transitionStyles = {
12  entering: { opacity: 1 },
13  entered:  { opacity: 1 },
14  exiting:  { opacity: 0 },
15  exited:   { opacity: 0 },
16};
17
18function FadeExample() {
19  const [inProp, setInProp] = useState(false);
20
21  return (
22    <div>
23      <button onClick={() => setInProp(!inProp)}>
24        {inProp ? 'Disappear' : 'Appear'}
25      </button>
26
27      <Transition in={inProp} timeout={duration}>
28        {state => (
29          <div style={{
30            ...defaultStyle,
31            ...transitionStyles[state]
32          }}>
33            Message from space
34          </div>
35        )}
36      </Transition>
37    </div>
38  );
39}

2.
CSSTransition

Extends

Transition
with automatic adding/removing of CSS classes, making it easier to create animations:

1import { CSSTransition } from 'react-transition-group';
2import React, { useState } from 'react';
3import './styles.css';
4
5function AlertExample() {
6  const [showAlert, setShowAlert] = useState(false);
7
8  return (
9    <div>
10      <button onClick={() => setShowAlert(prev => !prev)}>
11        {showAlert ? 'Hide alert' : 'Show alert'}
12      </button>
13
14      <CSSTransition
15        in={showAlert}
16        timeout={300}
17        classNames="alert"
18        unmountOnExit
19      >
20        <div className="alert">
21          <h4>Message from headquarters!</h4>
22          <p>A new planet suitable for colonization has been detected.</p>
23        </div>
24      </CSSTransition>
25    </div>
26  );
27}

And in the CSS file:

1/* styles.css */
2.alert {
3  background-color: #121a2b;
4  color: white;
5  padding: 20px;
6  border-radius: 8px;
7  box-shadow: 0 0 20px rgba(82, 161, 255, 0.5);
8  margin-top: 20px;
9}
10
11.alert-enter {
12  opacity: 0;
13  transform: scale(0.9);
14}
15
16.alert-enter-active {
17  opacity: 1;
18  transform: scale(1);
19  transition: opacity 300ms, transform 300ms;
20}
21
22.alert-exit {
23  opacity: 1;
24  transform: scale(1);
25}
26
27.alert-exit-active {
28  opacity: 0;
29  transform: scale(0.9);
30  transition: opacity 300ms, transform 300ms;
31}

3.
TransitionGroup

Manages a collection of

Transition
or
CSSTransition
components, ideal for animating lists:

1import { TransitionGroup, CSSTransition } from 'react-transition-group';
2import React, { useState } from 'react';
3import './styles.css';
4
5function StarsList() {
6  const [items, setItems] = useState([
7    { id: 1, name: 'Alpha Centauri' },
8    { id: 2, name: 'Proxima Centauri' },
9    { id: 3, name: 'Betelgeuse' }
10  ]);
11
12  const [nextId, setNextId] = useState(4);
13
14  const addStar = () => {
15    const newStars = [
16      'Sirius', 'Vega', 'Polaris', 'Canopus', 'Arcturus',
17      'Antares', 'Rigel', 'Procyon', 'Deneb'
18    ];
19    const randomStar = newStars[Math.floor(Math.random() * newStars.length)];
20
21    setItems([...items, { id: nextId, name: randomStar }]);
22    setNextId(nextId + 1);
23  };
24
25  const removeStar = id => {
26    setItems(items.filter(item => item.id !== id));
27  };
28
29  return (
30    <div>
31      <h2>Stars to visit</h2>
32      <button onClick={addStar}>Add star</button>
33
34      <TransitionGroup className="stars-list">
35        {items.map(({ id, name }) => (
36          <CSSTransition
37            key={id}
38            timeout={500}
39            classNames="star-item"
40          >
41            <div className="star-item">
42              <span>{name}</span>
43              <button onClick={() => removeStar(id)}>
44                &times;
45              </button>
46            </div>
47          </CSSTransition>
48        ))}
49      </TransitionGroup>
50    </div>
51  );
52}

CSS for this example:

1/* styles.css */
2.stars-list {
3  margin-top: 20px;
4}
5
6.star-item {
7  display: flex;
8  justify-content: space-between;
9  align-items: center;
10  padding: 12px 20px;
11  margin-bottom: 10px;
12  background-color: #0d1b2a;
13  border-radius: 8px;
14  color: #e0f7fa;
15  border-left: 4px solid #4fc3f7;
16}
17
18.star-item-enter {
19  opacity: 0;
20  transform: translateX(-30px);
21}
22
23.star-item-enter-active {
24  opacity: 1;
25  transform: translateX(0);
26  transition: opacity 500ms, transform 500ms;
27}
28
29.star-item-exit {
30  opacity: 1;
31}
32
33.star-item-exit-active {
34  opacity: 0;
35  transform: translateX(30px);
36  transition: opacity 500ms, transform 500ms;
37}

Advanced Techniques

1. Sequential Animations

Animating elements one after another for a cascading effect:

1import { CSSTransition, TransitionGroup } from 'react-transition-group';
2import React, { useState, useEffect } from 'react';
3import './styles.css';
4
5function LaunchSequence() {
6  const [steps, setSteps] = useState([]);
7  const allSteps = [
8    { id: 1, text: "Starting systems" },
9    { id: 2, text: "Main engine check" },
10    { id: 3, text: "Checking navigation systems" },
11    { id: 4, text: "Sealing hatches" },
12    { id: 5, text: "Countdown to launch" }
13  ];
14
15  useEffect(() => {
16    if (steps.length < allSteps.length) {
17      const timer = setTimeout(() => {
18        setSteps(prevSteps => [
19          ...prevSteps,
20          allSteps[prevSteps.length]
21        ]);
22      }, 1000);
23
24      return () => clearTimeout(timer);
25    }
26  }, [steps]);
27
28  return (
29    <div className="launch-sequence">
30      <h2>Launch Sequence</h2>
31      <TransitionGroup>
32        {steps.map(step => (
33          <CSSTransition
34            key={step.id}
35            timeout={500}
36            classNames="launch-step"
37          >
38            <div className="launch-step">
39              <div className="step-number">{step.id}</div>
40              <div className="step-text">{step.text}</div>
41            </div>
42          </CSSTransition>
43        ))}
44      </TransitionGroup>
45    </div>
46  );
47}

2. Animation Choreography

Synchronizing multiple animations to create complex sequences:

1import { Transition } from 'react-transition-group';
2import React, { useState } from 'react';
3
4function SpaceshipDashboard() {
5  const [active, setActive] = useState(false);
6
7  // Durations for different elements
8  const durations = {
9    background: 500,
10    systems: 400,
11    controls: 600,
12    status: 700,
13  };
14
15  // Styles for different states
16  const getStyles = (elementName, state) => {
17    const baseStyles = {
18      transition: `all ${durations[elementName]}ms ease-out`,
19    };
20
21    const elementSpecificStyles = {
22      background: {
23        entering: { opacity: 1, filter: 'brightness(1)' },
24        entered: { opacity: 1, filter: 'brightness(1)' },
25        exiting: { opacity: 0.5, filter: 'brightness(0.4)' },
26        exited: { opacity: 0.5, filter: 'brightness(0.4)' },
27      },
28      systems: {
29        entering: { opacity: 1, transform: 'translateY(0)' },
30        entered: { opacity: 1, transform: 'translateY(0)' },
31        exiting: { opacity: 0, transform: 'translateY(20px)' },
32        exited: { opacity: 0, transform: 'translateY(20px)' },
33      },
34      controls: {
35        entering: { opacity: 1, transform: 'scale(1)' },
36        entered: { opacity: 1, transform: 'scale(1)' },
37        exiting: { opacity: 0, transform: 'scale(0.8)' },
38        exited: { opacity: 0, transform: 'scale(0.8)' },
39      },
40      status: {
41        entering: { opacity: 1, transform: 'translateX(0)' },
42        entered: { opacity: 1, transform: 'translateX(0)' },
43        exiting: { opacity: 0, transform: 'translateX(-20px)' },
44        exited: { opacity: 0, transform: 'translateX(-20px)' },
45      },
46    };
47
48    return {
49      ...baseStyles,
50      ...elementSpecificStyles[elementName][state],
51    };
52  };
53
54  // Initial styles for elements
55  const initialStyles = {
56    background: { opacity: 0.5, filter: 'brightness(0.4)' },
57    systems: { opacity: 0, transform: 'translateY(20px)' },
58    controls: { opacity: 0, transform: 'scale(0.8)' },
59    status: { opacity: 0, transform: 'translateX(-20px)' },
60  };
61
62  return (
63    <div className="dashboard-container">
64      <button onClick={() => setActive(!active)}>
65        {active ? 'Shut down systems' : 'Start systems'}
66      </button>
67
68      <div className="spaceship-dashboard">
69        <Transition in={active} timeout={durations.background}>
70          {state => (
71            <div
72              className="dashboard-background"
73              style={{
74                ...initialStyles.background,
75                ...getStyles('background', state)
76              }}
77            />
78          )}
79        </Transition>
80
81        <Transition in={active} timeout={durations.systems}>
82          {state => (
83            <div
84              className="system-panels"
85              style={{
86                ...initialStyles.systems,
87                ...getStyles('systems', state)
88              }}
89            >
90              System Diagnostics
91            </div>
92          )}
93        </Transition>
94
95        <Transition in={active} timeout={durations.controls}>
96          {state => (
97            <div
98              className="control-panels"
99              style={{
100                ...initialStyles.controls,
101                ...getStyles('controls', state)
102              }}
103            >
104              Flight Controls
105            </div>
106          )}
107        </Transition>
108
109        <Transition in={active} timeout={durations.status}>
110          {state => (
111            <div
112              className="status-panels"
113              style={{
114                ...initialStyles.status,
115                ...getStyles('status', state)
116              }}
117            >
118              Status Monitors
119            </div>
120          )}
121        </Transition>
122      </div>
123    </div>
124  );
125}

Best Practices for Cosmically Smooth Animations

  1. Avoid animating computationally expensive properties - properties like

    width
    ,
    height
    , and
    top
    can cause reflow. Prefer
    transform
    and
    opacity
    .

  2. Use animation debuggers - tools like Chrome DevTools allow you to analyze animation performance.

  3. Think through start and end points - a well-designed animation has clearly defined states.

  4. Do not overdo the duration - animations should be short enough not to delay user interaction (usually 150-500ms).

  5. Add delays for complex sequences - use

    delay
    to create cascading effects.

  6. Ensure accessibility - remember users who may prefer reduced animations (

    prefers-reduced-motion
    ).

Example: Interstellar Portal

Let's create a portal component that animates the transition between "worlds" (views) in our cosmic application:

1import { CSSTransition } from 'react-transition-group';
2import React, { useState } from 'react';
3import './portal.css';
4
5function InterstellarPortal({ children, show, onExited }) {
6  return (
7    <CSSTransition
8      in={show}
9      timeout={1000}
10      classNames="portal"
11      unmountOnExit
12      onExited={onExited}
13    >
14      <div className="portal-container">
15        <div className="portal-content">
16          {children}
17        </div>
18      </div>
19    </CSSTransition>
20  );
21}
22
23function App() {
24  const [currentWorld, setCurrentWorld] = useState('home');
25  const [showPortal, setShowPortal] = useState(true);
26  const [nextWorld, setNextWorld] = useState(null);
27
28  const worlds = {
29    home: {
30      title: "Space Station Alpha",
31      content: "Welcome aboard Space Station Alpha, the command center of our interstellar operation."
32    },
33    mars: {
34      title: "Mars Colony One",
35      content: "Welcome to the first colony on Mars. Terraforming work is in progress."
36    },
37    europa: {
38      title: "Underwater Base Europa",
39      content: "Welcome to the research base beneath the icy shell of Jupiter's moon - Europa."
40    }
41  };
42
43  const navigateTo = (world) => {
44    setNextWorld(world);
45    setShowPortal(false);
46  };
47
48  const onPortalExited = () => {
49    setCurrentWorld(nextWorld);
50    setShowPortal(true);
51  };
52
53  return (
54    <div className="app">
55      <nav className="navigation">
56        <button onClick={() => navigateTo('home')} disabled={currentWorld === 'home'}>
57          Station Alpha
58        </button>
59        <button onClick={() => navigateTo('mars')} disabled={currentWorld === 'mars'}>
60          Mars
61        </button>
62        <button onClick={() => navigateTo('europa')} disabled={currentWorld === 'europa'}>
63          Europa
64        </button>
65      </nav>
66
67      <InterstellarPortal show={showPortal} onExited={onPortalExited}>
68        <h1>{worlds[currentWorld].title}</h1>
69        <p>{worlds[currentWorld].content}</p>
70      </InterstellarPortal>
71    </div>
72  );
73}

CSS for the portal:

1/* portal.css */
2.portal-container {
3  position: relative;
4  overflow: hidden;
5  min-height: 300px;
6  padding: 30px;
7  background: linear-gradient(135deg, #1a2a6c, #b21f1f, #fdbb2d);
8  border-radius: 12px;
9  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
10}
11
12.portal-content {
13  background: rgba(0, 0, 0, 0.7);
14  padding: 30px;
15  border-radius: 8px;
16  color: white;
17}
18
19/* Entry animation */
20.portal-enter {
21  opacity: 0;
22  transform: scale(0.7);
23}
24
25.portal-enter-active {
26  opacity: 1;
27  transform: scale(1);
28  transition: opacity 1000ms ease-out, transform 1000ms cubic-bezier(0.34, 1.56, 0.64, 1);
29}
30
31/* Exit animation */
32.portal-exit {
33  opacity: 1;
34  transform: scale(1);
35}
36
37.portal-exit-active {
38  opacity: 0;
39  transform: scale(1.3);
40  transition: opacity 1000ms ease-in, transform 1000ms cubic-bezier(0.36, 0, 0.66, -0.56);
41}
42
43/* Navigation style */
44.navigation {
45  display: flex;
46  justify-content: center;
47  gap: 15px;
48  margin-bottom: 30px;
49}
50
51.navigation button {
52  background-color: #2c3e50;
53  color: white;
54  border: none;
55  padding: 10px 15px;
56  border-radius: 5px;
57  cursor: pointer;
58  transition: all 0.3s;
59}
60
61.navigation button:hover:not(:disabled) {
62  background-color: #34495e;
63  transform: translateY(-2px);
64}
65
66.navigation button:disabled {
67  background-color: #95a5a6;
68  cursor: not-allowed;
69}

Practical Exercise: Create an Animated Planet Gallery

Design and implement an animated gallery of planets in our solar system. When a user navigates between planets, use React Transition Group to create smooth transitions between views. For each planet display:

  1. Planet name
  2. Planet image
  3. A few key facts (size, distance from the Sun, temperature)

Add the following animations:

  • Smooth transition between planets
  • Cascading effect for displaying planet facts
  • A "Back to overview" button with a transition animation

Remember that good animation enhances the user experience, not distracts from it. Animations should be smooth and natural, like the journey through space itself - elegant, majestic, and inspiring.

Go to CodeWorlds