useAnimationState

useAnimationState is a React hook for driving animations. It's an alternative to the animate prop.

It's useful when you:

  1. want the best performance on native,
  2. know your different animation states ahead of time, and
  3. don't mind using a hook
const animationState = useAnimationState({
from: {
opacity: 0,
scale: 0.9,
},
to: {
opacity: 1,
scale: 1.1,
},
expanded: {
scale: 2,
},
})
const onPress = () => {
if (animationState.current === 'to') {
animationState.transitionTo('expanded')
}
}
return <MotiView state={animationState} />

When to use this#

useAnimatedState lets you control your animation state based on static variants. It is the most performant way to drive animations, since it lives mutates shared values directly, instead of relying on JS state changes. You probably won't notice a performance difference, but you can keep it in your back pocket.

When using this hook, your animations are static, meaning they have to be known ahead of time. You can change the state by using transitionTo, but re-rendering your component does not update your variants.

Basic Usage#

Import#

import { MotiView, useAnimationState } from 'moti'

Moti exports typical react-native components, such as View, Text, etc.

Define your state#

const animationState = useAnimationState({
from: {
opacity: 0,
scale: 0.9,
},
to: {
opacity: 1,
scale: 1,
},
})

Pass state to your Moti component#

<MotiView state={animationState} />

Create your animationState, and pass it as your moti component's state prop.

Update state with transitionTo#

To change the animation variant, use the transitionTo function.

const animationState = useAnimationState({
from: {
opacity: 0,
scale: 0.9,
},
to: {
opacity: 1,
scale: 1,
},
active: {
scale: 1.1,
opacity: 1,
},
})
return (
<Pressable
onPress={() => {
animationState.transitionTo('active')
}}
>
<MotiView style={styles.shape} state={animationState} />
</Pressable>
)

You can also transition based on the current animation state, similar to setState from React's useState hook:

const animationState = useAnimationState({
from: {
opacity: 0,
scale: 0.9,
},
to: {
opacity: 1,
scale: 1,
},
active: {
scale: 1.1,
opacity: 1,
},
})
return (
<Pressable
onPress={() => {
// you can pass a function here
animationState.transitionTo((currentState) => {
if (currentState === 'from') {
return 'active'
}
return 'to'
})
}}
>
<MotiView style={styles.shape} state={animationState} />
</Pressable>
)

The function prop pattern isn't actually necessary, since updates are synchronous, unlike setState, which makes asynchronous updates. But I added this API because I'm used to it from setState and enjoy it.

It's worth noting that animationState.transitionTo will not trigger re-renders, so just keep that in mind.

Read the current state#

You could also read the current animation state, and use that to drive the next transiton, if you prefer:

const animationState = useAnimationState({
from: {
opacity: 0,
scale: 0.9,
},
to: {
opacity: 1,
scale: 1,
},
active: {
scale: 1.1,
opacity: 1,
},
})
return (
<Pressable
onPress={() => {
// you can read in the current state like this
if (animationState.current === 'from') {
animationState.transitionTo('active')
}
// or, like this, which achieves the exact same thing
animationState.transitionTo((currentState) => {
if (currentState === 'from') {
return 'active'
}
return currentState
})
}}
>
<MotiView style={styles.shape} state={animationState} />
</Pressable>
)

A full example#

import React from 'react'
import { useAnimationState, MotiView } from 'moti'
import { StyleSheet } from 'react-native'
export default function PerformantView() {
const animationState = useAnimationState({
from: {
opacity: 0,
scale: 0.9,
},
to: {
opacity: 1,
scale: 1,
},
})
return <MotiView style={styles.shape} state={animationState} />
}
const styles = StyleSheet.create({
shape: {
justifyContent: 'center',
height: 250,
width: 250,
borderRadius: 25,
backgroundColor: 'cyan',
},
})

Mount Animations#

const animationState = useAnimationState({
from: {
opacity: 0,
},
to: {
opacity: 1,
},
})
return <MotiView state={animationState} />

If both from and to are set, then it will transition from one to the other on the component's initial mount. If only from is set, this will be the initial state.

If you don't want mount animations, give your variants different names.

Don't destructure#

As a rule of thumb, don't destructure the animation state.

// ✅ do this
const state = useAnimationState(...)
// 🚨 not this
const { current, transitionTo } = useAnimationState(...)

Why?#

useAnimationState returns an object with a stable reference, but destructuring .current does not guarantee a stable reference.

You don't have to follow that suggestion if you don't want to. But I recommend it to prevent unintended consequences of triggering effects when these are used in dependency arrays.

If you aren't using the animator in a dependency array anywhere, then you can ignore this suggestion. But I treat it as a rule of thumb to keep things simpler.

Technically, it's fine if you do this with transitionTo. It's current you'll want to watch out for, since its reference will change, without triggering re-renders. This functions similar to useRef.

API#

Arguments#

  • variants (required)
    • an object with variants specifying your different static styles.
    • to achieve mount animations, pass a to and from variant here.
  • config
    • Optionally define your from and to variant keys.

If you need to rename to or from, do it like so:

// typically, it looks like this:
const animationState = useAnimationState({
from: {
opacity: 0,
},
to: {
opacity: 1,
},
})
// but you can customize it if you want:
const animationState = useAnimationState(
{
initial: {
opacity: 0,
},
next: {
opacity: 1,
},
},
{
from: 'initial',
to: 'next',
}
)

By default, similar to react-spring, you can pass a to and a from variant. from will always be the initial state, assuming you pass from. It is not required, though.

If, for some reason, you really don't want to use from and to as your props, you can pass a second argument object with from/to keys that rename them.

Returns#

  • current A synchronous way to read the current animation state. Returns the name of the current state (for example, to).
  • transitionTo(nextVariant) A function that lets you update the state.

You can pass the next state directly to it: `animationState.transitionTo('open')

Or you can pass it a function:

animationState.transitionTo((prevState) => {
if (prevState === 'open') {
return 'close'
}
return 'open'
})

Static animations only#

Unlike react-spring's useSpring, useAnimationState will not update your animation state if you change its style values.

// 🚨 bad, do not do this
const state = useAnimationState({
box: {
// 😡 this will not update!
opacity: isLoading ? 1 : 0,
},
})
return <MotiView state={state} />
// ✅ do this instead
<MotiView animate={{ opacity: isLoading ? 1 : 0 }} />

useAnimationState should only be used for static variants, meaning the different potential states you'll be animating to will be known ahead of time.

Any dynamic animations should be used with a component's animate prop directly.

For most cases, this is fine, but just keep that in mind. If you need styles that automatically update based on React's state, use a component's animate prop instead of this hook.

...ok, but#

Now that I got those warnings out of the way, I'll clarify: technically, you can use dynamic variables in this hook. However, only calling transitionTo will change the actual style. So if you use dynamic variables, just know that they won't apply to your styles until you call transitionTo.