Controllers & Springs

Note

This is an advanced guide into the workings of react-spring with some prerequisites of your own knowledge. You should be confident with classes & Typescript before reading this.

What you know

If you've already used the useSpring hook then you'll be familiar with the following code:

const [styles, api] = useSpring(() => ({
x: 0,
y: 0,
backgroundColor: '#ff0000',
scale: [1, 1, 1],
config: {
precision: 0.0001,
},
}))

If we look only at the return value from our hook we can see it's an array with two items styles and api. We'll initially focus on what styles are. This is an object of SpringValues where the key of said object correlates to the animatable keys referenced in the config object you passed (either in the way above or as part of the from config object). The value of said key is a SpringValue.

Explained in typescript terms the signature looks like:

type SpringValues<SpringConfig extends Record<string, any>> = {
[Key in keyof OnlyAnimatableKeys<SpringConfig>]: SpringValue
}

In the example above, OnlyAnimatableKeys would narrow SpringConfig to only include the keys x, y, backgroundColor, scale. Then because we know they're animatable, they will therefore be SpringValues note this is a simplified version of the types in the library.

The second item in the array, api is a SpringRef which is an abstraction around the methods of the Controller class. However, a SpringRef can manage multiple Controllers. For more information, see Imperative Api.

Controller

So where does the Controller come into all this? Well, every "spring" is in fact, a Controller. Therefore, when you use the hook useSpring you initialise a new Controller class and when you pass the number X to the useSprings hook, you're creating X amount of Controllers.

These Controllers manage the SpringValues you create in your config object. It's methods are very similar to that of the SpringValue class, the primary methods used such as start, stop, pause run through the array of managed SpringValues and call the exact same method:

// The set method from the Controller class
set(values) {
for (const key in values) {
const value = values[key]
if (!is.und(value)) {
this.springs[key].set(value)
}
}
}
// Would be used like this
controller.set({
x: 0,
scale: [0,0,0]
})

The signature of useSpring hook's config object is identical to the Controller class constructor first argument. You can therefore draw the conclusion that the useSpring hook handles the lifecycle of the Controller class in the react environment and adds the controllers to a provided (or generated in the hook) SpringRef, providing a very straight forward and clear interface for managing one or more Controller classes. Meaning, if you so choose, you could omit using a hook and instead use Controller class directly!

For a more detailed API description, see the advanced api reference entry for Controller.

Spring value

SpringValues are what you normally interact with, they're the props that are specifically passed to the animated component, they can be interpolated and don't necessarily need to be named after properties of the element they're being used on:

const {
backgroundColor, // SpringValue<string>
o, // SpringValue<number>
trans, // SpringValue<number[]>
} = useSpring({
backgroundColor: '#00ff00',
o: 0,
trans: [0, 1, 2],
})

This is because the names you give are just the keys used in the Controller and the SpringValue only cares about the type of value you're passing. Inside the SpringValue class we have the entire lifecycle of the animation, from the event handlers being called to the type of advancement being used (e.g. spring physics or duration) the SpringValue is the driving force for your animation.

In addition to controlling a "Fluid value" – a value that changes over time (see Fluids for more information). SpringValues also apply their updates to the animated node they're passed to. You might recall if you had read the animated elements concept page that on creation of the animated component, we convert any SpringValues into AnimatedValues which seems like a one way direction, in fact we attach this AnimatedValue to the original SpringValue, which allows the spring to update the animated node (e.g. <animated.p>.) via the animated value when the advance function is called on the SpringValue via our rafz package. SpringValue is able to do this because it extends the FrameValue which is a kind of FluidValue.

// Taken from `@react-spring/core/src/SpringValue.ts
class SpringValue<T = any> extends FrameValue<T>
// Taken from `@react-spring/core/src/FrameValue.ts
abstract class FrameValue<T = any> extends FluidValue< T, FrameValue.Event<T> >
// Taken from `@react-spring/shared/src/FluidValue.ts`
abstract class FluidValue<T = any, E extends FluidEvent<T> = any>

Similar to the Controller class because it does not rely on react internals (this is how we animate outside the react render system) you can use this class directly, however the lifecycle events that are associated with Controllers & hooks will not be applied and is something you would need to manage yourself. For a more detailed API description of SpringValue, see the advanced api reference entry for SpringValue.

Frame value

The first thing you'll notice about the FrameValue class is that is considered "abstract", this is a feature unique typescript and means that you cannot instantiate the class directly. The point of an abstract class is to provide a base class that other classes can extend from in their shape (methods / properties) but usually require some additional implementation. In the case of FrameValue we have the advance method which is abstract, the SpringValue class extends FrameValue and implements it's own advance method. You can read more about abstract classes here.

Fluids

Fluids is a small glue layer for observable events. It allows parent nodes send events to their children creating an event-driven system. Therefore, when a FluidObserver has an event observered, it can perform an action. In the case of SpringValue, the _start function is called, thus animating the SpringValue's value, which in turn animates the animated node you're passing this value too.

Fluids are used all over this library, in the case of our animated HOC, we use Fluids to schedule animated updates with our rafz package.

If you want to learn more about Fluids, I recommend looking at our source code!

What you learnt

You've probably learnt alot about how the internals of react-spring works! But more importantly, you've specifically learnt about these things:

  • How the useSpring hook works under the hood
  • What is a Controller & SpringValue
  • How we have an event-driven system embeded in the library!