@fernandorojo/react-native-shared-animation
v1.0.1
Published
A nimble global animtion-state management tool for react-native's `Animated` values. Think of it like a simple redux for react-native animation values.
Downloads
5
Readme
🕺 react-native-shared-animation
A global animation-state management tool for react-native's Animated
values. Think of it like a simple redux for react-native animation values.
This package is super easy to use and requires no more than 1 minute of learning.
Also supports react-native-reanimated
and react-native-gesture-handler
.
Why?
Sharing animated values across components should be easy. However, it currently requires so much prop drilling that any complex animation in react-native
becomes a hassle to manage.
🤔 When might you use this?
Any time you find yourself passing a certain animation value from one component to another more than once, react-native-shared-animation
feels like a breath of fresh air.
Example use case
Maybe you have a component with a ScrollView
, and a header you want to react to its scroll position, but the two components aren't that close together code-wise. If this were the case, without this library, have to declare an animated value high up in the component tree, and then pass it down the three through many layers of components.
This library aims to fix that.
I also think this could help make it easy to achieve shared transitions across screens. I haven't put together an example for that yet, so if you do, please submit a PR :)
Syntax
Much of the syntax is similar to react-redux
, but if you aren't familiar with how redux works, don't worry; this is much simpler.
Quick setup
The boilerplate setup takes about 15 seconds and is similar to redux in how it works. Just wrap your entire app with the <SharedAnimationProvider />
component.
App.js
import React from 'react'
import { SharedAnimationProvider } from 'react-native-shared-animation'
import Animated from 'react-native-reanimated'
import App from './src/App'
export default () => {
const animatedValues = { myCoolAnimatedValue: new Animated.Value(0) }
return (
<SharedAnimationProvider animatedValues={animatedValues}>
<App />
</SharedAnimationProvider>
)
}
In some other nested component, all you'd need to do is this:
import React from 'react'
import { useSharedAnimation } from 'react-native-shared-animation'
import Animated from 'react-native-reanimated'
export default () => {
// here we get the value from our global store using react hooks
const { getValue } = useSharedAnimation();
const coolValue = getValue('myCoolAnimatedValue');
return <Animated.View style={{ width: coolValue }} />
}
You can also use the connectSharedAnimation
HOC or the <SharedAnimation />
component if you don't want to use the useSharedAnimation
hook.
🎉 All set. Your app is now ready to share animated values across components.
Below I'll expand on all the ways that you're able to 1) initialize animated values and 2) access animated values.
Examples
To see full examples, go to the /examples folder. You can also see the Expo snack of examples here.
Installation
To install, open your react native repository in the terminal and run this command:
npm i react-native-shared-animation
You could use yarn if you prefer that:
yarn react-native-shared-animation
Recommended: If you want to use react-native-reanimated
for animations, run this afterwards:
npm i react-native-reanimated react-native-gesture-handler
This works with...
✅ react-native-reanimated
✅ Animated
from react-native
if you prefer that.
✅ Expo
✅ Typescript
✅ react-native-gesture-handler
1) Initializing shared animated values
You have two options for initializing shared animation values: global initialization or on-the-fly initializiation in components.
i) [Recommended] Initialize global animated values
Simply pass an animatedValues
object as a prop to the <SharedAnimationProvider />
component. This will act as the initial set of animated values.
You can initialize as many animated values as you'd like.
App.js
import React from 'react'
import { SharedAnimationProvider } from 'react-native-shared-animation'
import Animated from 'react-native-reanimated'
import App from './src/App' // path to your root component
export default () => {
const mainScrollValue = new Animated.Value(0)
const animatedValues = { mainScrollValue }
<SharedAnimationProvider animatedValues={animatedValues}>
<App />
</SharedAnimationProvider>
}
🐻 That's it! Your app now has globally-accessible animated values.
In this case, mainScrollValue
can be accessed by any component.
If you come from a redux background, you can think of this like setting the initial store value.
Why this is the better option:
From a style perspective, it is useful to know what values will be accessible across your app upon initialization. And when it comes to performance, this is less prone to bugs, since you'll never try to access a value that hasn't been initialized.
That said, you also have the newValue
method to your disposal, as described in the next option.
ii) [Careful] Initialize animated values on the fly in components
You can also initialize animated values directly in components. The thing is, this option is more prone to bugs, since you might try to access an animated value before it's been initialized.
Overall, I'd suggest only using this one on a case-by-case basis.
It can be achieved with the newValue(name, value)
function, documented below.
2) Accessing animated values
Here's the fun part.
There are 3 ways to access animated values
These are the 3 options you have:
useSharedAnimation
hook: TheuseSharedAnimation
hook is super simple (and is my favorite to use). Only works in function components. See react hooks to learn more.connectSharedAnimation
HOC: You can use theconnectSharedAnimation
higher-order component. Useful for class components and function components. Good for taking animation logic out of a component, too.SharedAnimation
component: You can also wrap any component with<SharedAnimation />
to connect it to the global animation state.
Option 1: useSharedAnimation
Call useSharedAnimation
in the root of a function component.
Example:
...
import { useSharedAnimation } from 'react-native-shared-animation'
export default () => {
const { getValue, newValue, animatedValues } = useSharedAnimation();
const scroll = getValue('scroll')
// same as...
const { scroll } = animatedValues;
return <Animated.View style={{ ..., translateX: scroll }} />
}
So simple!
🤑🤑
If you're using a version of react / react-native that supports hooks, you can happily stop reading the docs here and just use this.
--
Option 2: connectSharedAnimation(mapValuesToProps)(Component)
You can also use the connectSharedAnimation
higher-order component to pass animated values as props.
This option gives you some customization options beyond the useSharedAnimation
, such as taking global animated code out of your actual component.
This HOC passes newValue
, getValue
, and any animated values you choose as props to your Component
.
Example:
...
import { connectSharedAnimation } from 'react-native-shared-animation'
const ConnectedComponent = ({ getValue, newValue, scroll }) => {
return <Animated.View style={{ ..., translateX: scroll }} />
}
// determine which values you want to pass to this component
const mapValuesToProps = animatedValues => ({
scroll: animatedValues.scroll
})
// could also have done this:
// const mapValuesToProps = 'scroll'
// ...or this:
// const mapValuesToProps = ['scroll', 'someOtherValue']
export default connectSharedAnimation(mapValuesToProps)(ConnectedComponent)
mapValuesToProps
(required)
This is the first and only argument for connectSharedAnimation
. It determines which animated values will be passed to the component as direct props.
This value can be either a string
, array of strings
, a function
, or null
.
mapValuesToProps as a string
The string should correspond to an existing global animated value.
...
import { connectSharedAnimation } from 'react-native-shared-animation'
const ConnectedComponent = ({ getValue, newValue, scroll }) => {
return <Animated.View style={{ ..., translateX: scroll }} />
}
const mapValuesToProps = 'scroll'
export default connectSharedAnimation(mapValuesToProps)(ConnectedComponent)
mapValuesToProps as an array of strings
Enter the names of multiple global animated values you want passed as direct props.
...
import { connectSharedAnimation } from 'react-native-shared-animation'
const ConnectedComponent = ({ getValue, newValue, scroll, someOtherValue }) => {
return <Animated.View style={{ ..., translateX: scroll }} />
}
// const mapValuesToProps = ['scroll', 'someOtherValue']
export default connectSharedAnimation(mapValuesToProps)(ConnectedComponent)
mapValuesToProps a function
A function that takes in animatedValues
as its first argument and returns an object that references these values. This can be useful if you want to abstract some animation logic out of your component.
...
import { connectSharedAnimation } from 'react-native-shared-animation'
const ConnectedComponent = ({ getValue, newValue, scroll, opacity }) => {
return <Animated.View style={{ ..., translateX: scroll }} />
}
const mapValuesToProps = animatedValues => {
const { scroll } = animatedValues;
const opacity = interpolate(scroll, {
inputRange: [0, 400],
outputRange: [1, 0]
})
return {
scroll,
opacity
}
}
export default connectSharedAnimation(mapValuesToProps)(ConnectedComponent)
The function works the same as redux's mapStateToProps
.
mapStateToValues as null
You can pass null
to it if you don't want any animated values explicitly passed to the component. If you pass null
, you will still have access to getValue
and newValue
.
Option 3: SharedAnimation
Component
A basic component that uses the render props method. (Similar in principle to the [Query]()
component from react-apollo
.)
...
import { SharedAnimation } from 'react-native-shared-animation'
export default () => {
return (
<SharedAnimation>
{({ getValue, newValue }) => {
const scroll = getValue('scroll')
return (
<YourComponent scroll={scroll} />
)
})}
</SharedAnimation>
)
}
getValue(name)
A function that takes in the name of a global animated value and returns the animated value itself.
This is the most important function that you'll find yourself using all the time.
Example
const SomeComponent = () => {
const { getValue } = useSharedAnimation()
const opacity = getValue('opacity')
return (
<Animated.View style={{ opacity }} />
)
}
animatedValues
A dictionary containing the current global state of animated values. You can use this to access the global store directly, but I recommend using getValue
instead, since it has some added convenience checks.
Example
const SomeComponent = () => {
const { animatedValues } = useSharedAnimation()
const { opacity } = animatedValues
return (
<Animated.View style={{ opacity }} />
)
}
newValue(name, value)
A function that creates a new global animated value. Takes a name as the first argument, and an animated value (or node) as the second argument.
Returns: the animated value it just created
Example
const SomeComponent = () => {
const { newValue } = useSharedAnimation();
const opacity = newValue('opacity', new Animated.Value(1))
return (
<Animated.View style={{ opacity }} />
)
}
Documentation Recap
<SharedAnimationProvider />
| Prop | Required | Type | Example |
|---|---|---|---|
| animatedValues
| no (but recommended) | dictionary
| { scroll: new Animated.Value(0) }
|
| children
| yes | React.Node
| Your app JSX should be a child component of this provider. |
Illustrative example
Sharing animated values across your entire app is as easy as this:
import React from 'react'
import Animated from 'react-native-reanimated'
import { SharedAnimationProvider, useSharedAnimation} from 'react-native-shared-animation'
export default function App() {
const animatedValues = { scroll: new Animated.Value(0) }
return (
<SharedAnimationProvider animatedValues={animatedValues}>
<ComponentWithScrollView />
<OtherComponentThatAccessesScroll />
</SharedAnimationProvider>
)
}
const OtherComponentThatAccessesScroll = () => {
const { getValue } = useSharedAnimation()
const scroll = getValue('scroll')
return <Animated.View style={{ translateX: scroll }} />
}
const ComponentWithScrollView = () => {
const { getValue } = useSharedAnimation()
const scroll = getValue('scroll')
const onScroll = Animated.event([
{
nativeEvent: {
contentOffset: {
y: scroll
}
}
}
])
return (
<Animated.ScrollView onScroll={onScroll} />
)
}
Yup, that's it. No prop drilling at all.