Search code examples
reactjsreact-hooksreact-spring

How to use react-spring from react component classes


I am trying to import react-spring animation library to a reactjs application which is based on react component classes.

It seems that new (as of 2019) React Hooks made some integration messier.

So that is why I am asking how to use react-spring which in turn uses react hooks, in a ReactJS application what uses classes.

The code that does not work properly looks like:

import React from 'react';
import { useSpring, animated, interpolate } from 'react-spring'


export default class TestAnimation extends React.Component {

    constructor(props) {
        super(props);

        const { o, xyz, color } = useSpring({
            from: { o: 0, xyz: [0, 0, 0], color: 'red' },
            o: 1,
            xyz: [10, 20, 5],
            color: 'green'
        });

        this.aniText = <animated.div
            style={{
                // If you can, use plain animated values like always, ...
                // You would do that in all cases where values "just fit"
                color,
                // Unless you need to interpolate them
                background: o.interpolate(o => `rgba(210, 57, 77, ${o})`),
                // Which works with arrays as well
                transform: xyz.interpolate((x, y, z) => `translate3d(${x}px, ${y}px, ${z}px)`),
                // If you want to combine multiple values use the "interpolate" helper
                border: interpolate([o, color], (o, c) => `${o * 10}px solid ${c}`),
                // You can also form ranges, even chain multiple interpolations
                padding: o.interpolate({ range: [0, 0.5, 1], output: [0, 0, 10] }).interpolate(o => `${o}%`),
                // Interpolating strings (like up-front) through ranges is allowed ...
                borderColor: o.interpolate({ range: [0, 1], output: ['red', '#ffaabb'] }),
                // There's also a shortcut for plain, optionless ranges ...
                opacity: o.interpolate([0.1, 0.2, 0.6, 1], [1, 0.1, 0.5, 1])
            }}
        >
            {o.interpolate(n => n.toFixed(2)) /* innerText interpolation ... */}

        </animated.div>
    };


    render() {
        return <div>
            {this.aniText}
        </div>;
    }
}

which results this error:

Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app

Solution

  • You can't use hooks inside class components. So, you could either split out the animated component into its own functional component, which would look like this:

    import React from 'react';
    import { useSpring, animated, interpolate } from 'react-spring'
    
    const AniText = ()=> {
      const { o, xyz, color } = useSpring({
        from: { o: 0, xyz: [0, 0, 0], color: 'red' },
        o: 1,
        xyz: [10, 20, 5],
        color: 'green'
      });
    
      return (<animated.div
        style={{
            // If you can, use plain animated values like always, ...
            // You would do that in all cases where values "just fit"
            color,
            // Unless you need to interpolate them
            background: o.interpolate(o => `rgba(210, 57, 77, ${o})`),
            // Which works with arrays as well
            transform: xyz.interpolate((x, y, z) => `translate3d(${x}px, ${y}px, ${z}px)`),
            // If you want to combine multiple values use the "interpolate" helper
            border: interpolate([o, color], (o, c) => `${o * 10}px solid ${c}`),
            // You can also form ranges, even chain multiple interpolations
            padding: o.interpolate({ range: [0, 0.5, 1], output: [0, 0, 10] }).interpolate(o => `${o}%`),
            // Interpolating strings (like up-front) through ranges is allowed ...
            borderColor: o.interpolate({ range: [0, 1], output: ['red', '#ffaabb'] }),
            // There's also a shortcut for plain, optionless ranges ...
            opacity: o.interpolate([0.1, 0.2, 0.6, 1], [1, 0.1, 0.5, 1])
        }}
      >
        {o.interpolate(n => n.toFixed(2)) /* innerText interpolation ... */}
    
      </animated.div>)
    
    }
    
    export default class TestAnimation extends React.Component {
    
        constructor(props) {
            super(props);
        }
    
        render() {
            return <div>
                <AniText />
            </div>;
        }
    }
    

    OR, if you want to stick with a class component, react-spring exports a render-props API as well, which is completely valid inside any React component, class or otherwise:

    import React from "react";
    import { Spring, animated, interpolate } from "react-spring/renderprops";
    
    export default class TestAnimation extends React.Component {
      constructor(props) {
        super(props);
      }
    
      render() {
        return (
          <div>
            <Spring
              native
              from={{ o: 0, xyz: [0, 0, 0], color: "red" }}
              to={{ o: 1, xyz: [10, 20, 5], color: "green" }}
            >
              {({ o, xyz, color }) => (
                <animated.div
                  style={{
                    // If you can, use plain animated values like always, ...
                    // You would do that in all cases where values "just fit"
                    color,
                    // Unless you need to interpolate them
                    background: o.interpolate(o => `rgba(210, 57, 77, ${o})`),
                    // Which works with arrays as well
                    transform: xyz.interpolate(
                      (x, y, z) => `translate3d(${x}px, ${y}px, ${z}px)`
                    ),
                    // If you want to combine multiple values use the "interpolate" helper
                    border: interpolate(
                      [o, color],
                      (o, c) => `${o * 10}px solid ${c}`
                    ),
                    // You can also form ranges, even chain multiple interpolations
                    padding: o
                      .interpolate({ range: [0, 0.5, 1], output: [0, 0, 10] })
                      .interpolate(o => `${o}%`),
                    // There's also a shortcut for plain, optionless ranges ...
                    opacity: o.interpolate([0.1, 0.2, 0.6, 1], [1, 0.1, 0.5, 1])
                  }}
                >
                  {// Finally, this is how you interpolate innerText
                  o.interpolate(n => n.toFixed(2))}
                </animated.div>
              )}
            </Spring>
          </div>
        );
      }
    }
    

    Here is a codesandbox with the two solutions side-by-side: https://codesandbox.io/s/8ynxyowzk0