React Spring Tutorial: Making Animated React Apps

Incorporating animation to UI design can be a tricky thing. Last year, we published an article describing “butter-smooth” animations in a React app made with Framer Motion. The issue with Framer Motion was the lack of tutorials on how to do something beyond the simplest stuff. Studying the React Spring library can make people face the opposite problem. Although its documentation is well-ordered, detailed, easy to use, and has a lot of impressive examples, most of them are too complicated if your aim is to get the basics. So we thought that making some React Spring tutorials could be helpful.

This article will be useful to those starting out with React Spring. We’ll be looking over various methods for combining multiple springs for more complicated React animations, examples included. Note that a basic knowledge of React is required to understand this article. Knowing how to use styled-components / emotion / any other similar library would also be helpful.

5 hooks of the React Spring library 

Animations add life to design and improve the UX of an app.

React Spring is a physics-based animation library. It calculates all the mechanics behind the scenes for nice and smooth animation and allows to configure it for a user both partially and completely. What’s more, React Spring makes it easier than using pure CSS (at least, without an insanely complicated and hard-to-maintain bulk of code). 

At the moment, the React Spring library contains 5 hooks:

  1. useSpring – a single animation that changes the state of the animation a => b.
  2. useSprings – multiple spring animations for lists that also change the animation state a => b.
  3. useTrail – multiple spring animations with a common data set, where each subsequent animation trails behind the previous one.
  4. useTransition – animations on mount/unmount for lists, where the elements get added, deleted and updated.
  5. useChain – used to determine the order and sequence of execution of several previously defined animations.

All these 5 hooks of the React Spring library are similar in nature, but each of them has its own peculiarity. Let’s examine every one of them individually in more detail to learn how to apply them for making beautiful animations in React.

useSpring

We’ll start this React Spring animation tutorial with the simplest hook – useSpring. It transforms the passed values into animated ones.

As the first example, we’ll create a simple animation component, that will resize and shift the backgroundPosition on click.

First, let’s import the required hook and animated component:

For animation on appearance it will be enough to indicate the following:

import { animated, useSpring } from "React Spring";

For animation on appearance it will be enough to indicate the following:

const springProps = useSpring({
   from: { opacity: 0, ... },
   to: { opacity: 1, ... }
 })

Spring allows animating the state changes from the initial from to the final to. 

Besides, the to prop can be omitted to make it more simple:

const springProps = useSpring({
   opacity: 1,
   from: { opacity: 0 },
 })

You can animate almost any CSS property or use arbitrary names, excluding the reserved keywords.

Next, you’ll need to pass springProps to the animated element. It’s worth mentioning though, that since the transmitted values are getting updated, they cannot be used with ordinary components or tags.

The following styled-components / emotion / etc component is required:

const AnimatedBox = styled(animated(ComponentName/TagName))`...`;

Or just any HTML tag, with the addition of the “animated” prefix: 

<animated.button>Button</animated.button>

Animated is an animation-primitive that allows you to handle the passed animated props. Animated extends the capabilities of native elements so that they can accept animated values.

What’s left is to pass the prop to the required component:

<AnimatedBox style={{springProps}} />

React Spring hooks can take almost any value or color, HTML attribute, and even a string pattern (for example, borderTop: “6px solid red”), etc. One very unfortunate limitation is that it’s impossible to transfer different properties for from and to in a single prop. That is, px must be converted to px, % => %, number => number, etc., or an error will occur.

As you can see from the example below, animation states can be destructured if the need arises. This is useful if, for example, you need to describe the animation states for several components at once, when using arbitrary names or for interpolations (we’ll elaborate on it a bit later).

We’ll use useState to change the animation states. Since we pass both true and false values for animated props with the ternary operator, we don’t necessarily have to define initial from values for this particular case, but it’s better to be safe than sorry and define it anyway for error-proof. Otherwise, the prop data will be empty before making changes to the state, and animation might break or cause an unexpected result.

const [clicked, setClicked] = useState(false);
 const { size, ..., ...springProps } = useSpring({
   size: clicked ? 300 : 200,
   backgroundPosition: clicked ? "50% 100%" : "50% 0%",
   ...
   from: {
     size: 200,
     backgroundPosition: "50% 0%"
     ...
   }
 });

We’ll destructure the value of size (it will be needed for animating both width and height) and pass the other values, in this case, the backgroundPosition, to springProps.

It remains only to pass the animated values to our component, and, of course, change its state on click:

<AnimatedItem
  style={{ height: size, width: size, ...springProps }}
  onClick={() => setClicked(!clicked)}
/>

Now, after being clicked, the state will change from false to true, so the height and width of the component will change from 200px to 300px, and the backgroundPosition of the background element will also change from “50% 0%” to “50% 100%”.

Perhaps it’s worth mentioning the counter for the ProgressBar used in this example. Let’s complete our hook with the following line of code:

counter: clicked ? 100 : 0,

It’s important not to forget to destructure the counter, as we’ll need it outside of the springProps passed to the AnimatedItem component.

To iterate over counter values from 0 to 100, we’ll use some interpolation:

<AnimatedBox>
  {counter.interpolate(val => Math.floor(val) + "%")}
</AnimatedBox>

To put it another way, the interpolation allows you to iterate over values of a function or of a specified range of values. Note that the interpolation transferred to the animated primitive works more efficiently and takes up less space.

Quite simple, right?

useSprings

import { animated, useSprings } from “react-spring”;

The useSrings hook is slightly different from the previous hook. It creates multiple spring animations for static lists.

In this React Spring example, the colorful boxes are our list, which uses useSprings. And the larger block is a regular useSpring, which receives the index value of the required color on clicking the list element and passes its index into state.

const [index, setIndex] = useState(null);

useSprings takes the length of our list and determines the animation parameters for each of the list elements:

const springs = useSprings(list.length, list.map(item => ({ 
   ... 
}));

useSprings in this example:

const springs = useSprings(
   colorScheme.length,
   colorScheme.map((item, i) => ({
     background: item.hex,
     color: item.fontColor,
     opacity: index === null | i === index ? 1 : 0.6,
     height: index === null ? 120 : 60,
     from: {
       opacity: 0,
       height: 120
     }
   }))
 );

The background and color values are taken from the array of colorScheme objects:

const colorScheme = [
  { name: "Red munsell", hex: "#ec0b43", fontColor: "#fff" },
  ...
];

The springs parameters may seem confusing at first glance, but in reality, it’s quite simple. Before we click on one of the list elements (that is, when index = null), the opacity value for all colored blocks = 1. If one of the list elements has already been clicked, then the opacity of the current element = 1. And the opacity for all the other elements = 0.6. In the case of the height value, it’s even simpler. Before we click on one of the color blocks, its value = 120, after being clicked = 60.

We still got to pass the value of springs to the list of components:

<GridContainer pt={1}>
  {springs.map((prop, i) => (
    <AnimatedItem
      key={i}
      onClick={() => { setIndex(i); onItemClick(i); }}
      style={prop}
    />
  ))}
</GridContainer>

Where the index state is set and onItemClick function is executed:

 const [springProps, setSpringProps] = useSpring(() => ({
   from: { height: 0, opacity: 0 }
 }));
 
 const onItemClick = i => {
   const { name, hex, fontColor } = colorScheme[i];
   setSpringProps({
     name,
     background: hex,
     color: fontColor,
     height: 200,
     opacity: 1
   });
 };

Here we use the second method to determine useSpring – via the set function – setSpringProps. On click, we pass the index – i of the selected list item to the onItemClick function, which receives the values of the desired item (name, hex, fontColor) and updates the useSpring using these values, and also sets the height and opacity.

It remains only to pass the values of springProps to the component:

<AnimatedItem style={springProps}>
  <AnimatedBox>{springProps.name}</AnimatedBox>
  <AnimatedBox>
    {index !== null && colorScheme[index].hex}
  </AnimatedBox>
</AnimatedItem>

The hex value, in this case, is taken directly from the original array of objects, because if we take this value from spring, we get color interpolation in RGBA format, which isn’t exactly what we intended.

useTrail

import { animated, useTrail, interpolate } from “react-spring”;

useTrail allows you to create multiple spring animations with a single config, and each subsequent spring is executed after the previous one. It is used for staggered animation.

const trail = useTrail(list.length, { ... });

For this example the config looks approximately like this:

const [trail, set] = useTrail(imgList.length, () => ({
  opacity: 1, ...
  from: {
    opacity: 0, ...
  }
}));

Passing the trail into the component:

const [trail, set] = useTrail(imgList.length, () => ({
  opacity: 1, ...
  from: {
    opacity: 0, ...
  }
}));

What you should pay attention to in this example is the interpolation for a set of values, which allows you to change the transform property for several transform-functions at once. It differs slightly from the single parameter interpolation:

transform: x.interpolate(x => `translateX(${x}px)`)

But the principle is the same.

As a result, we got a trail animation, that changes the values of transform property (scale, translate and skewX) and opacity on appearance, and also changes the scale once again when the list container is clicked. By clicking the button, the opacity will also be changed with the help of the already familiar to us set function.

useTransition

import { animated, useTransition } from “react-spring”;

useTransition allows you to create an animated transition group. It takes in the elements of the list, their keys, and lifecycles. The animation is triggered on appearance and disappearance of the elements. 

You can use transition for arrays, to toggle between components, or for mounting/unmounting of the same component.

Let’s create a slider with the help of useTransition:

const [[index, dir], setIndex] = useState([0, 0]);
const transitions = useTransition(slides[index], item => item.url, {
  from: {
    opacity: 0,
    transform: `translate3d(${dir === 1 ? 100 : -100}%,0,0) scale(0.5)`
   },
  enter: { ... },
  leave: { ... }
});

Using an already familiar principle, we’ll transfer the length of the list to useTransition (since we are animating the slider, the index of the current slide will serve as length) and the list element itself (the URL of the background image).

The from, enter, leave properties describe the state of the current slide from from to enter on mount and from enter to leave on unmount.

Let’s pass our config to the component:

{transitions.map(({ item, prop, key }) => (
  <Box key={key}>
    <Slide
      style={prop}
      background={`url(${item.url}`}
   />
  </Box>
))}

The value of index and the direction of slides (dir) change on clicking the arrow controls:

<Arrow onClick={() => slideLeft()}/> 
<Arrow onClick={() => slideRight()}/>

Where slideLeft and slideRight are:

const slideLeft = () => setIndex([(index - 1 + slides.length) % slides.length, -1]);
const slideRight = () => setIndex([(index + 1) % slides.length, 1]);

slideLeft and slideRight allow not only changing the value of index and dir, but also resetting it after reaching the last slide.

As a result, we got a slider where the slides get mounted/unmounted, changing the values of opacity and transform when index and dir get transformed. 

Control bullets are animated using the already familiar useSrings hook, so we won’t dwell on them.

useChain

useChain allows you to set the execution sequence of previously defined animation hooks. To do this, you need to use refs, which will subsequently prevent the independent execution of the animation.

import { animated, useChain } from “react-spring”;

Let’s use this hook to animate for the hamburger menu element. In our case, first, the useSpring animation of the MenuBar will execute, followed by useSprings animation for the list of Menu components – MenuItem.

Defining the ref for the useSpring and using the same principle, for the useSprings:

const springRef = useRef();
 const { ... , ...springProps } = useSpring({
   ref: springRef,
   ...
 });

All we have to do now is to define the order of execution of animations with useChain:

useChain(
   open ? [springRef, springsRef] : [springsRef, springRef],
   open ? [0, 0.25] : [0, 0.75]
 );

In this example, if the state is open === true, then on expanding the menu the animation for the MenuBar will execute first, followed by MenuItems. If open === false, that is, on closing the menu, the execution order is reversed.

After setting the order of animations, you can also specify timeSteps – an array of values between 0-1 (1 = 1000ms) that defines the beginning and end of the animations. For example, [0, 0.25] 0 * 1000ms = 0 and 0.25 * 1000ms = 250ms.

Bonus

A few bonus examples for this tutorial using the already familiar React Spring hooks:

Accordion – useSpring

Cards list – useSprings

Image gallery – useChain

React-spring v9.0

We have very exciting news for you, guys: v9.0 is pretty much 90% done 🎉

Disclaimer: try not to use the latest rc (release candidate) version in production or at least use it with extreme caution. Learn more about updates on the breaking changes page.

Oddly enough, the animation feature many people were looking forward to the most — infinite loop —  turned out to be probably the simplest thing ever. Yet, there wasn’t a proper way to do it before.

useSpring + loop

Property use cases:

  1. loop: true – repeat animation
  2. Pass a function (loop: () => 3> n ++) which will be called after each loop iteration (return true to continue looping, false to stop)
  3. Define a loop object (loop: {reverse: true}) for a separate customization of the loop animation. It may contain any useSpring properties, but the most interesting one is reverse: true, which prevents twitching when loop is repeated.
import { animated, useSpring } from “react-spring”;

Let’s configure the already familiar useSpring using the brand-new and shiny ✨ loop property:

const springProps = useSpring({
   loop: { reverse: true },
   from: { y: 0, rotate: 0 },
   to: { y: 100, rotate: 180 },
   config: { duration: 2500 }
 });

All that remains is to pass the config to our animated component:

<AnimatedItem style={springProps} />

That’s it! We got ourselves a seamless and easy way to configure loop animation. Can you imagine now that we couldn’t do it properly before?

Points to take away

Advantages of the React Spring library:

  • Animations are based on physics. There is no need (unless you intentionally want to) to customize duration or easing. The result is smooth, soft, and natural-looking animations.
  • Easy-to-use and clear documentation.
  • A lot of interesting and beautiful demos in the documentation + on the React Spring creator Paul Henschel’s CodeSandbox page.
  • If necessary, you can utilize a very useful set of hooks from use-gesture.
  • The library repository is continually being updated and maintained.
  • A small, but quite active community (spectrum, discord) has formed around the library.
  • Paul Henschel regularly shares interesting insights, demos, and more on his Twitter page.

Disadvantages of the React Spring library:

  • There is no simple and effective way to loop the animation (in current stable v8).
    Examples from the documentation suggest using an infinite loop that in practice causes significant performance problems:
const { value } = useSpring({
   to: async next => {
     while (1) await next({ ... })
   },
   ...
 })

  • As we’ve already mentioned, another very unfortunate limitation of React Spring is that it’s impossible to transfer different properties for from and to in a single prop. That is, px must be converted to px, % => %, number => number, etc., or an error will occur.
  • Also, the library doesn’t allow you to animate the value of auto. The documentation suggests using react-resize-aware, react-measure, etc. for these, and other purposes that require the exact height and width of a component.

Now that you have an understanding of a new and relatively easy way to work with animations in React and this React Spring animation tutorial, try animating various aspects of your application. Let’s do it!

Written by Julia Shikanova and Kate Shokurova

* * *

This React Spring tutorial was originally published in March 2019 and was updated in December 2020 to make it more relevant and comprehensive.

A battle-ready Development Team
Building for crazy startups and reputable businesses.
Written by Kate Shokurova
December 12, 2020

YOU MAY ALSO LIKE