
In this tutorial, we'll create an animated collapsable card usingreact-native-reanimated
. We'll be starting from a provided template, which can be found atthis 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 thetemplate
branch:
git clone https://github.com/dimaportenko/reanimated-collapsable-card-tutorial.gitcd reanimated-collapsable-card-tutorialgit checkout template
Adding React Native Reanimated
We will be using thereact-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 yourbabel.config.js
:
module.exports=function(api){api.cache(true);return{presets:['babel-preset-expo'],plugins:['react-native-reanimated/plugin'],};};
Implementation
In ourListItem.tsx
, we will add a new state for the height of the collapsable content:
const[height,setHeight]=useState(0);constanimatedHeight=useSharedValue(0);
We calculate the collapsible content height in theonLayout
callback:
constonLayout=(event:LayoutChangeEvent)=>{constonLayoutHeight=event.nativeEvent.layout.height;if(onLayoutHeight>0&&height!==onLayoutHeight){setHeight(onLayoutHeight);}};
We'll create an animated style for our collapsable content:
constcollapsableStyle=useAnimatedStyle(()=>{animatedHeight.value=expanded?withTiming(height):withTiming(0);return{height:animatedHeight.value,};},[expanded,height]);
We'll wrap our collapsable content in anAnimated.View
:
<Animated.Viewstyle={[collapsableStyle,{overflow:'hidden'}]}><Viewstyle={{position:'absolute'}}onLayout={onLayout}><Textstyle={[styles.details,styles.text]}>{item.details}</Text></View></Animated.View>
To make our code more maintainable, let's refactor theCollapsableContainer
into a separate reusable component:
importReact,{useState}from"react";import{LayoutChangeEvent,View,Text}from"react-native";importAnimated,{useAnimatedStyle,useSharedValue,withTiming,}from"react-native-reanimated";exportconstCollapsableContainer=({children,expanded,}:{children:React.ReactNode;expanded:boolean;})=>{const[height,setHeight]=useState(0);constanimatedHeight=useSharedValue(0);constonLayout=(event:LayoutChangeEvent)=>{constonLayoutHeight=event.nativeEvent.layout.height;if(onLayoutHeight>0&&height!==onLayoutHeight){setHeight(onLayoutHeight);}};constcollapsableStyle=useAnimatedStyle(()=>{animatedHeight.value=expanded?withTiming(height):withTiming(0);return{height:animatedHeight.value,};},[expanded,height]);return(<Animated.Viewstyle={[collapsableStyle,{overflow:"hidden"}]}><Viewstyle={{position:"absolute"}}onLayout={onLayout}>{children}</View></Animated.View>);};
Then, we can use our newCollapsableContainer
component in theListItem
component:
exportconstListItem=({item}:{item:ListItemType})=>{const[expanded,setExpanded]=useState(false);constonItemPress=()=>{setExpanded(!expanded);};return(<Viewstyle={styles.wrap}><TouchableWithoutFeedbackonPress={onItemPress}><Viewstyle={styles.container}><Imagesource={{uri:item.image}}style={styles.image}/><Viewstyle={styles.textContainer}><Textstyle={styles.text}>{item.title}</Text><Textstyle={styles.text}>{item.subtitle}</Text></View></View></TouchableWithoutFeedback><CollapsableContainerexpanded={expanded}><Textstyle={[styles.details,styles.text]}>{item.details}</Text></CollapsableContainer></View>);};
That's it! You have successfully created an animated collapsable card in React Native usingreact-native-reanimated
. This animated component provides a smooth user experience, and the separateCollapsableContainer
component can be reused in different parts of your application. Happy coding!Final code
Top comments(6)

Hi,
first of all, thank you for the great tutorial!
The only problem I run into is when the "expanded" value is set to true on mount, nothing is displayed, I think it's because thecollapsableStyle
is not recomputed.
How could this be resolved?

- LocationKharkiv, Ukraine
- Joined
Hey,
try to add height to the dependencies like
const collapsableStyle = useAnimatedStyle(() => { animatedHeight.value = expanded ? withTiming(height) : withTiming(0); return { height: animatedHeight.value, }; }, [expanded, height]);
I believe on the first renderheight
is 0 and it's not triggered to recalculate on height value updated.

Thanks man! I had a similar solution, but what did the trick for me was your suggestion of using { overflow: 'hidden' }, to ensure we would get the full height.

- LocationKharkiv, Ukraine
- Joined
Welcome!

- LocationKharkiv, Ukraine
- Joined
if you'll share your code on github or expo snack, I can take a look.
For further actions, you may consider blocking this person and/orreporting abuse