Collapsible Card with React Native Reanimated
Dima Portenko
Posted on July 10, 2023
In this tutorial, we'll create an animated collapsable card using react-native-reanimated
. We'll be starting from a provided template, which can be found at this GitHub link. The template contains an Expo project with a FlatList. Each list item has an image, title, and description. Our goal is to make the description collapsable with a smooth animation.
## Getting Started
First, clone the project from GitHub and switch to the template
branch:
git clone https://github.com/dimaportenko/reanimated-collapsable-card-tutorial.git
cd reanimated-collapsable-card-tutorial
git checkout template
Adding React Native Reanimated
We will be using the react-native-reanimated
library to create our animations. To add it, run the following command:
npx expo install react-native-reanimated
Then, you'll need to update your babel.config.js
:
module.exports = function (api) {
api.cache(true);
return {
presets: ['babel-preset-expo'],
plugins: ['react-native-reanimated/plugin'],
};
};
Implementation
In our ListItem.tsx
, we will add a new state for the height of the collapsable content:
const [height, setHeight] = useState(0);
const animatedHeight = useSharedValue(0);
We calculate the collapsible content height in the onLayout
callback:
const onLayout = (event: LayoutChangeEvent) => {
const onLayoutHeight = event.nativeEvent.layout.height;
if (onLayoutHeight > 0 && height !== onLayoutHeight) {
setHeight(onLayoutHeight);
}
};
We'll create an animated style for our collapsable content:
const collapsableStyle = useAnimatedStyle(() => {
animatedHeight.value = expanded ? withTiming(height) : withTiming(0);
return {
height: animatedHeight.value,
};
}, [expanded, height]);
We'll wrap our collapsable content in an Animated.View
:
<Animated.View style={[collapsableStyle, {overflow: 'hidden'}]}>
<View style={{ position: 'absolute' }} onLayout={onLayout}>
<Text style={[styles.details, styles.text]}>{item.details}</Text>
</View>
</Animated.View>
To make our code more maintainable, let's refactor the CollapsableContainer
into a separate reusable component:
import React, { useState } from "react";
import { LayoutChangeEvent, View, Text } from "react-native";
import Animated, {
useAnimatedStyle,
useSharedValue,
withTiming,
} from "react-native-reanimated";
export const CollapsableContainer = ({
children,
expanded,
}: {
children: React.ReactNode;
expanded: boolean;
}) => {
const [height, setHeight] = useState(0);
const animatedHeight = useSharedValue(0);
const onLayout = (event: LayoutChangeEvent) => {
const onLayoutHeight = event.nativeEvent.layout.height;
if (onLayoutHeight > 0 && height !== onLayoutHeight) {
setHeight(onLayoutHeight);
}
};
const collapsableStyle = useAnimatedStyle(() => {
animatedHeight.value = expanded ? withTiming(height) : withTiming(0);
return {
height: animatedHeight.value,
};
}, [expanded, height]);
return (
<Animated.View style={[collapsableStyle, { overflow: "hidden" }]}>
<View style={{ position: "absolute" }} onLayout={onLayout}>
{children}
</View>
</Animated.View>
);
};
Then, we can use our new CollapsableContainer
component in the ListItem
component:
export const ListItem = ({ item }: { item: ListItemType }) => {
const [expanded, setExpanded] = useState(false);
const onItemPress = () => {
setExpanded(!expanded);
};
return (
<View style={styles.wrap}>
<TouchableWithoutFeedback onPress={onItemPress}>
<View style={styles.container}>
<Image source={{ uri: item.image }} style={styles.image} />
<View style={styles.textContainer}>
<Text style={styles.text}>{item.title}</Text>
<Text style={styles.text}>{item.subtitle}</Text>
</View>
</View>
</TouchableWithoutFeedback>
<CollapsableContainer expanded={expanded}>
<Text style={[styles.details, styles.text]}>{item.details}</Text>
</CollapsableContainer>
</View>
);
};
That's it! You have successfully created an animated collapsable card in React Native using react-native-reanimated
. This animated component provides a smooth user experience, and the separate CollapsableContainer
component can be reused in different parts of your application. Happy coding! Final code
Posted on July 10, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.