The Definitive Guide to React-Spring useTransition

Deconstructing useTransition examples from the documentation and creating an original example

useTransition is a powerful tool for animating a list of text, images, or components with react-spring. However, I found the documentation and examples listed on the official site to be challenging for a new react-spring user to understand. Since I wanted a detailed explanation, I created one.

Together, we’ll deconstruct two of the more advanced examples from the react-spring useTransitions page. After that, I’ll show an original example of a ball juggling animation.

List Reordering

The first animation example from the docs is by Paul Henschel and demonstrates the random visual shuffling of a list.

The useTransition API is fairly complex. Before engaging the code, I recommend including TypeScript if you fork the above Codesandbox. Seeing the typing for the useTransition hook will help you understand the code.

Take a look at the code in the Codesandbox. In Henschel’s example, a list of objects is imported into a state object ( the rows array) and then a useEffect hook uses lodash’s shuffle method to regularly shuffle the list of object. Nothing spring-y about that.

However, the useTransition hook quickly comes into play. In this case, useTransition() is passed three parameters.

  1. rows.map(data => ({ …data, y: (height += data.height) — data.height })) This prop was a bit confusing to me at first, but it simply takes each object from the rows array, adds a y property, and returns a new array populated with these new objects.
  2. d => d.name This sets the key for each object returned from the useTransitions hook.
  3. The below sets the props and state for each object returned by useTransition. The props are height, opacity, and y. The state will be from, leave, enter, or update depending on the state the UI is in.
{
from: { height: 0, opacity: 0 },
leave: { height: 0, opacity: 0 },
enter: ({ y, height }) => ({ y, height, opacity: 1 }),
update: ({ y, height }) => ({ y, height })
}

Pausing and looking at dev tools after the useTransition hook is called, we see the following (the object at position 1 is expanded).

Transitions Object in Dev Tools

Notice that the state is “update” in the screenshot.

The transitions object returned from useTransition is then mapped over to generate the JSX. There are three interesting things to note here:

  1. animated.div is a react-spring ‘native’ element that will properly trigger the AnimateValue props generated by useTransition.
  2. In the style parameter, zIndex is cleverly set so that each item has a unique z value. This keeps items from conflicting when animating over one another.
  3. The style transform value calls y.interpolate. This is a function provided by react-spring for taking a value and transforming it across a range or according to a function. In this case interpolate is taking the y value and injecting it into a string to be returned as a value for styling.

Multistage Transitions

The next example from the docs is also by Paul Henschel and demonstrates multistage transitions. In this case, multistage means multiple types of transtions on each item and multiple items being transition over time. This demonstration applies a sequence of transitions to each item in an array of strings.

I’ll deconstruct it: on render, the useEffect, useCallback, and useRef hooks are cleverly used to populate an items array. The array is populated over time…the ref actually contains a series of setTimeout functions that fire over time to populate the items array with different strings with no user interaction.

The react-spring useTransition hook receives the following parameters:

  1. The items string array, similar to the List Reordering example.
  2. No keys are received. This is pretty interesting, because I assumed that would mean the index of the items array is the key. However, the string values of the items array are actually the keys. Without this default, the values ‘Apples’ and ‘Kiwis’ would have undesired transitions applied to them. In other words, watch the animation and ask yourself why ‘Kiwi’ simply slides from position three to position two without the specified opacity or transforms being applied.
  3. An object populated with CSSProperties values that becomes the props and state of the returned transitions array objects.
The transitions object after the useTransitions hook is called
The transitions object after the useTransitions hook is called

The useTransitions hook returns an array which is then iterated over with the .map function to generate a list of <animated.div…/> JSX elements. I logged the key + item pair to see how many renders occured:

Console Output from testing useTransitions

Notice how the Apple and Kiwi keys do not change.

My Original Example: Ordered Transitions (aka Ball Juggling)

Taking what I have learned from the examples and documentation on react-spring useTransition, I created a Code Sandbox ball juggling animation with useTransition.

Here is an analysis of what I did:

const originalData = cloneDeep(data);
const [rows, set] = useState(data);useEffect(
  () =>
    void setInterval(() => {
      set(rotateItems(rows));
    }, 500),
    []
);

const rotateItems = (list) => {
  list.unshift(list.pop());
  return setPosition(list);
}

I needed access to the original values and order of the data array for later use. A shallow copy was not good enough because I needed to change values in objects within the array, so I made a deep copy of the original array.

In the useEffect hook, I created an interval for rotating the balls. Then, like the List Reordering example, I had a defined list that I wanted to rotate. However, I needed it to rotate in the specific pattern where the last item gets popped from the end of the array and ‘unshifted’ to the beginning of the array.

const setPosition = (list) => {
return list.map((item, index) => {
item.x = originalData[index].x;
item.y = originalData[index].y;
return item;
});
}

In the above method, the data array has been rotated. I accessed the originalData array (made from the deep copy) to set the x and y value of each item to the appropriate value for its new index (for example, the 0 position item always has an x,y value of 0,0).

Here’s an example:

{
name: 'Blue',
css: 'blue',
x: 0,
y: 0
},
{
name: 'Red',
css: 'red',
x: 200,
y: 0
},
{
name: 'Yellow',
css: 'yellow',
x: 100,
y: 200
}

becomes →

{
name: 'Yellow',
css: 'yellow',
x: 0,
y: 0
},
{
name: 'Blue',
css: 'blue',
x: 200,
y: 0
},
{
name: 'Red',
css: 'red',
x: 100,
y: 200
}

Next up is the useTransition hook:

const transitions = useTransition(
rows.map((data) => ({ ...data })),
(d) => d.name,
{
enter: ({ x, y }) => ({ x, y }),
update: ({ x, y }) => ({ x, y })
}
);

Using enter or from have the same result. update is necessary to actually have the animation play. The heavy lifting was done above in the x,y updating; here I simply hook into react-spring.

return (
<div className="list">
{transitions.map(({ item, props: { x, y }, key }) => {
return (
<animated.div
key={key}
className="cell"
style={{
transform: interpolate([x, y], (x, y) => `translate(${x}px, ${y}px)`),
background: item.css
}}
/>
);
})}
</div>
)

The most interesting code in the JSX is the interpolate function. This is imported from react-spring. Notice the syntax for being able to interpolate over two values at once.

This example doesn’t get deeply into the spring-physics aspect of react-spring. However, useTransition is a critical hook to understand in order to get desired functionality out of react-spring.

Resources:

YouTube version of this article.

Docs: https://www.react-spring.io/docs/hooks/use-transition

Ball Juggling useTransition example: https://codesandbox.io/s/react-spring-usetransition-ball-juggle-4ocf4

Super useful TypeScript cheat sheet for react-spring: https://www.saltycrane.com/cheat-sheets/typescript/react-spring/latest/

Here’s an Intro to Framer Motion, a competitor animation library with similar transition functionality.

Share this post:

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.