Noncommital Premarital Mischief

    @fernandorojo/react-native-shared-animation

    1.0.1 • Public • Published

    🕺 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:

    1. useSharedAnimation hook: The useSharedAnimation hook is super simple (and is my favorite to use). Only works in function components. See react hooks to learn more.

    2. connectSharedAnimation HOC: You can use the connectSharedAnimation higher-order component. Useful for class components and function components. Good for taking animation logic out of a component, too.

    3. 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.

    Keywords

    none

    Install

    npm i @fernandorojo/react-native-shared-animation

    DownloadsWeekly Downloads

    1

    Version

    1.0.1

    License

    MIT

    Unpacked Size

    34.1 kB

    Total Files

    27

    Last publish

    Collaborators

    • fernandorojo