React Native — Animation With Props

React Native’s built in Animation library makes creating animated UI elements a breeze. But implementing animations in every component’s stategets tedious and greatly reduces reusability. While in the process of designing an app for a customer, you want to create a button that has two different states: enabled and disabled.

For the seasoned React Native developer, this would be an extremely simple task. In the customLargeButton component, there would need to be a simple ternary statement on the style props such that they match the disabled prop. Something like that might be coded as such:

export default class LargeButton extends Component {
render() {
return (
<View
style={{
backgroundColor: "#4A90E2",
opacity: this.props.disabled ? 0.3 : 1,
justifyContent: "center",
alignItems: "center",
borderRadius: 10,
shadowRadius: this.props.disabled ? 0 : 2,
shadowOffset: {
width: 0,
height: this.props.disabled ? 0 : 2,
},
shadowOpacity: 0.4,
shadowColor: "#000",
...this.props.style,
}}
>
<Text
style={{
fontSize: 24,
color: "white",
}}
>
{this.props.text}
</Text>
</View>
)
}
}
LargeButton.defaultProps = {
text: "Button",
disabled: false,
}

Note: Here I am using View to demonstrate the styling, but in practice you could use TouchableOpacity or wrap the View in a TouchableWithoutFeedback

Perfect, our job is done! Right? Well, technically this is a working component, but something seems off.

Everything happens so…instantly. This may be the desired result in some scenarios, but generally, the end user is going to expect an animation. In the real world, nothing happens instantly, everything takes time. To make an app feel natural, animations are a must.

To implement animations into our existing component. We need to do a few items:

  • Implement an animated value in the state
  • Watch the props for updated values
  • Create interpolations on the component styles

To create animations that are based on props, the Animated.Value() initial value needs to be based on what the initial props are. The initiation in statewould look like this.

state = {
disabledAnimation: new Animated.Value(this.props.disabled ? 1:0)
}

Our second task is to watch for change on the animation-triggering props. For us, this prop is disabled . We can utilize the componentWillReceivePropshandler to detect when disabled changes. We want to trigger the animations when disabled changes so we will setup the function like this:

componentWillReceiveProps(nextProps){
if(nextProps.disabled !== this.props.disabled){
Animated.timing(this.state.disabledAnimation,{
duration: 300,
toValue: nextProps.disabled ? 1:0 //Same as Animated.Value
}).start()
}
}

Never forget the .start() at the end of the Animated.timing , it’s even more annoying than a semicolon because it won’t even throw an error, your animation will just not work!

You might also notice that we have a presumably unnecessary check to see if nextProps.disabled is different than this.props.disabled . Technically we could just always call the Animated.timing function and let that run, but as React Native developers, the key to a smooth app is to cross the native bridge as few times as possible. So if a check can be done in JS, then its worth it.

Our final task is to link our disabledAnimation variable to the styles of the component. To make our component animatable, we need to change it from a View to an Animated.View . Then we need to utilize .interpolate() on the disabledAnimation variable to tell our view what the styles need to be at either side of the animation. After these modifications, our component will look like this:

render() {
return (
<Animated.View
style={{
backgroundColor: "#4A90E2",
opacity: this.state.disabledAnimation.interpolate({
inputRange: [0, 1],
outputRange: [1, 0.3],
}),
justifyContent: "center",
alignItems: "center",
borderRadius: 10,
shadowRadius: this.state.disabledAnimation.interpolate({
inputRange: [0, 1],
outputRange: [2, 0],
}),
shadowOffset: {
width: 0,
height: this.state.disabledAnimation.interpolate({
inputRange: [0, 1],
outputRange: [2, 0],
}),
},
shadowOpacity: 0.4,
shadowColor: "#000",
...this.props.style,
}}
>
<Text
style={{
fontSize: 24,
color: "white",
}}
>
{this.props.text}
</Text>
</Animated.View>
)
}

And there we have it! Now our component has beautiful animations based entirely off of a prop value. Now all of the logic for the animations is contained within the component, allowing us to reuse this component anywhere while still keeping the animations!