A card flip animation in React Native with Hooks

Background

Components in React Native

import { Image, View, StyleSheet } from "react-native";function Card( { dispatch, card, game } ) {  ...  return (
<View
style={ style.cardWrapper }
>
<Image
style={ style.cardFront }
source={ require( ... ) }
/>
<Image
style={ style.cardBack } }
source={ require( ... ) }
/>
</View>
);
}const style = StyleSheet.create( {
cardWrapper: { ... },
cardFront: {
...
position: "absolute",
},
cardBack: {
...
backfaceVisibility: "hidden"
},
};
  • The basic building block of React Native is the View, which functions as an all-purpose div or section tag. Native also gives us other familiar DOM elements like Text, TextInputs, Switches, Buttons and several kinds of Lists in addition to the Images we see above. Soon we’ll respond to finger touches by replacing View with a component called Pressable— an update to an existing component called Touchable.
  • You can probably tell from the dispatch prop that yes, a dialect of Redux is available in React Native — again, different from Redux in React. A full tutorial on Redux in React Native is beyond this article’s scope so I’m not covering it. Consult the Redux docs or find one of the many great tutorials floating around if you’d like to learn more.
  • We don’t style React Native with CSS: it gives us a StyleSheet class instead, initialized with a JS object where each style is a key; values are themselves objects nearly identical to React style objects, with options like margin and position and display familiar from CSS. We then apply those styles to a component with the style={} attribute, which is broadly similar to className in React.
  • Notice in our component, we have two images ( for the card front and back); see that in our StyleSheet we place the cardFront on top with position: "absolute" and use backfaceVisibility to hide the cardBack when the card is flipped.
  • Assets like local images must be require()d! Also note that you must hard-write sources for every require(): you can’t use require() with an interpolated string in React Native, as in: require( `./assets/images/logos/${ colorName }.png` ).

The Animated component

import { Animated, Image, View, StyleSheet } from "react-native";function Card( { dispatch, card, game } ) {const flipAnimation = useRef( new Animated.Value( 0 ) ).current;let flipRotation = 0;
flipAnimation.addListener( ( { value } ) => flipRotation = value );
const flipToFrontStyle = {
transform: [
{ rotateY: flipAnimation.interpolate( {
inputRange: [ 0, 180 ],
outputRange: [ "0deg", "180deg" ]
} ) }
]
};
const flipToBackStyle = {
transform: [
{ rotateY: flipAnimation.interpolate( {
inputRange: [ 0, 180 ],
outputRange: [ "180deg", "360deg" ]
} ) }
]
};
...return ( ... );}
  • First let’s import Animated and create a new Animated.Value() that starts at 0. Animated.Value()s help us keep track of where we are in an animation. Before hooks, we would have done this by creating something like this.animation— but here we’ll use the hook equivalent, useRef().current. I’ve said before that useRef() is a godsend in desktop React and it’s just as useful here.
  • We’re also keeping track of where we are in an animation using Animated.Value().addListener(), which does… exactly what it says on the box, giving us use of a flipRotation variable so we can tell if a card has been flipped yet. Note that in addListener, the { value } must be deconstructed from the callback’s arguments!
  • We need some additional styles, one each for the front and back, and unfortunately the nesting here is an affront to decency. (You can define more variables but I don’t think it helps much.) Each style is an object with a single key, transform, corresponding to an array of transform objects.
  • Keep in mind, transforms like rotateY need text strings as values — so these rotateY objects use a function called Animated.Value().interpolate(), which translates an Animated.Value() to a string of anywhere from "0deg" to "180deg"to "360deg" (depending on where we happen to be in the animation). Pretty cool, huh?
import { Animated, Image, View, StyleSheet } from "react-native";function Card( { dispatch, card, game } ) {const flipAnimation = useRef( new Animated.Value( 0 ) ).current;let flipRotation = 0;
flipAnimation.addListener( ( { value } ) => flipRotation = value );
const flipToFrontStyle = { ... };const flipToBackStyle = { ... };const flipToFront = () => {
Animated.timing( flipAnimation, {
toValue: 180,
duration: 300,
useNativeDriver: true,
} ).start();
};
const flipToBack = () => {
Animated.timing( flipAnimation, {
toValue: 0,
duration: 300,
useNativeDriver: true,
} ).start();
};
...return ( ... );}
import { Animated, Pressable, StyleSheet } from "react-native";function Card( { dispatch, card, game } ) {const flipAnimation = useRef( new Animated.Value( 0 ) ).current;let flipRotation = 0;
flipAnimation.addListener( ( { value } ) => flipRotation = value );
const flipToFrontStyle = { ... };const flipToBackStyle = { ... };const flipToFront = () => { ... };const flipToBack = () => { ... };...return (
<Pressable
style={ style.cardWrapper }
onPress={ () => !!flipRotation ? flipToBack() : flipToFront() }
>
<Animated.Image
style={ { ...style.cardFront, ...flipToBackStyle } }
source={ require( <card front>) }
/>
<Animated.Image
style={ { ...style.cardBack, ...flipToFrontStyle } }
source={ require( <card back> ) }
/>
</Pressable>
);
}

Conclusion

--

--

--

Oh geez, Josh Frank decided to go to Flatiron? He must be insane…

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

ES6 JavaScript

Pcom | Final

Implement Stack and Queue data structure in JavaScript

Angular Practices in 2022 that No One Talks About

Exploring GraphQL

REST API using MongoDB, Express.js and Node.js

A TypeScript tale — Interfaces, Classes & Generics

Vue Login Animation

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Josh Frank

Josh Frank

Oh geez, Josh Frank decided to go to Flatiron? He must be insane…

More from Medium

Text strings must be rendered within a <Text> component. ReactNative

React Native & Typescript | Imports & Exports

Re.Pack in React-Native

Working with Stack Navigation in React Native with Typescript