Wrapping React Spring's useSpring Hook (A Use Case for Wrapping External Libraries)

In this post, I'd like to aim for similar improvements with React Spring's useSpring hook.

The useSpring hook you to animate an element's style by controlling its style from when first appears to when the animation completes:

// some-component.js

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

const spring = useSpring({
  from: { opacity: 0 },
  to: { opacity: 1 },
});

// ...

<animated.div style={spring}>Hello World</animated.div>

Without an abstraction, there is no way to make the animations reusable.

One way to make the animation reusable is to create a file that exports an object associating a { from, to, ...etc } config (the argument that useSpring takes) with an animation name:

// animations.js
export default {
  fadeIn: {
    from: { opacity: 0 },
    to: { opacity: 1 },
  },
};

// some-component.js

import { animated, useSpring } from 'react-spring';
import animations from './animations';

const spring = useSpring(animations.fadeIn);

// ...

<animated.div style={spring}>Hello World</animated.div>

This is the easiest solution, but we have to inconveniently import from two places.

We can improve upon this by export react-spring modules plus our animations object from a single file:

// animations.js
export const animations = {
  fadeIn: {
    from: { opacity: 0 },
    to: { opacity: 1 },
  },
};

export * from 'react-spring';

// some-component.js

import { animated, animations, useSpring } from './animations';

const spring = useSpring(animations.fadeIn);

// ...

<animated.div style={spring}>Hello World</animated.div>

We can improve upon this even more by not having to import animated, animations, and useSpring, and then scope animations.fadeIn to useSpring.

Instead, we can expose use[AnimationName] hooks that return all that we need:

// animations.js
import { animated, useSpring } from 'react-spring';

const animations = {
  fadeIn: {
    from: { opacity: 0 },
    to: { opacity: 1 },
  },
};

export function useFadeIn() {
  const spring = useSpring(animations.fadeIn);
  return {
    animated,
    spring,
  };
}

// some-component.js

import { useFadeIn } from './animations';

const { animated, spring } = useFadeIn();

// ...

<animated.div style={spring}>Hello World</animated.div>

Alternatively to creating a hook for every animation, you could expose a more generic but similar useSpring wrapper:

// animations.js
import { animated, useSpring as useBaseSpring } from 'react-spring';

const animations = {
  fadeIn: {
    from: { opacity: 0 },
    to: { opacity: 1 },
  },
};

export const PRESETS = Object.freeze(Object.keys(animations));
export function useSpring({ preset } = {}) {
  const spring = useBaseSpring(animations[preset]);
  return {
    animated,
    spring,
  };
}

// some-component.js

import { PRESETS, useSpring } from './animations';

const { animated, spring } = useSpring({ preset: PRESETS.fadeIn });

// ...

<animated.div style={spring}>Hello World</animated.div>

Personally, I like creating a hook for every animation, just as you would create a CSS class to encapsulate a certain set of styles.

🎉 We've brainstormed ways to improve upon React Spring's useSpring hook.

How would you wrap this hook?

17