React Spring Tutorial: Making Animated React Apps

Incorporating animation to UI design can be a tricky thing. Last December 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 animations, examples included.

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 a crazy-complicated, 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.

useSpring

We’ll start with the simplest one – useSpring. It transforms the passed values into the animated ones.

If we console.log the values returned by spring, we get an object containing the animated props:

For 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. Note that although we use useState, you still need to set the initial state of the animation in the prop from. Otherwise, the prop data will be empty before making changes to the state.

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 example, the color scheme list is our list, which uses useSprings. And the larger block is the usual 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 state index is defined on click 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 apply the second method to determine useSpring – using 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 (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 set function we already know.

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 it.

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 using the already familiar React Spring hooks:

Accordion – useSpring

Cards list – useSprings

Image gallery – useChain

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 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.
    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, try animating various aspects of your application. Let’s do it!

Written by Julia Shikanova and Kate Shokurova

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

YOU MAY ALSO LIKE

This is the old design of Shakuro.com

We're currently working on rebranding our company and redesigning the website