React native Flatlist does not scroll inside the custom Animated Bottom sheet

Viewed 2844

I have created one custom Animated bottom sheet. User can move the bottom sheet scroll up and down. Inside my bottom sheet, I have used flatList where I fetched the data and render the items as a card. Up-till now everything works as expected but I had an issue Flatlist scrolling. Inside the bottom sheet the Flat-list does not scroll. I have made hard coded height value 2000px, which is really practice and also FlatList's contentContainerStyle added hard coded paddingBottom 2000(also another bad practice). I want to scroll the FlatList based on Flex-box. I don't know how to fix this issue.

I share my code on expo-snacks

This is my all code

import React, { useState, useEffect } from "react";
import {
  StyleSheet,
  Text,
  View,
  Dimensions,
  useWindowDimensions,
  SafeAreaView,
  RefreshControl,
  Animated,
  Button,
  FlatList,
} from "react-native";

import MapView from "react-native-maps";
import styled from "styled-components";

import {
  PanGestureHandler,
  PanGestureHandlerGestureEvent,
  TouchableOpacity,
} from "react-native-gesture-handler";

const { width } = Dimensions.get("screen");

const initialRegion = {
  latitudeDelta: 15,
  longitudeDelta: 15,
  latitude: 60.1098678,
  longitude: 24.7385084,
};

const api =
  "http://open-api.myhelsinki.fi/v1/events/?distance_filter=60.1699%2C24.9384%2C10&language_filter=en&limit=50";

export default function App() {
  const { height } = useWindowDimensions();
  const [translateY] = useState(new Animated.Value(0));

  const [event, setEvent] = useState([]);
  const [loading, setLoading] = useState(false);

  // This is Fetch Dtata
  const fetchData = async () => {
    try {
      setLoading(true);
      const response = await fetch(api);
      const data = await response.json();

      setEvent(data.data);
      setLoading(false);
    } catch (error) {
      console.log("erro", error);
    }
  };
  useEffect(() => {
    fetchData();
  }, []);

  // Animation logic
  const bringUpActionSheet = () => {
    Animated.timing(translateY, {
      toValue: 0,
      duration: 500,
      useNativeDriver: true,
    }).start();
  };

  const closeDownBottomSheet = () => {
    Animated.timing(translateY, {
      toValue: 1,
      duration: 500,
      useNativeDriver: true,
    }).start();
  };

  const bottomSheetIntropolate = translateY.interpolate({
    inputRange: [0, 1],
    outputRange: [-height / 2.4 + 50, 0],
  });

  const animatedStyle = {
    transform: [
      {
        translateY: bottomSheetIntropolate,
      },
    ],
  };

  const gestureHandler = (e: PanGestureHandlerGestureEvent) => {
    if (e.nativeEvent.translationY > 0) {
      closeDownBottomSheet();
    } else if (e.nativeEvent.translationY < 0) {
      bringUpActionSheet();
    }
  };

  return (
    <>
      <MapView style={styles.mapStyle} initialRegion={initialRegion} />
      <PanGestureHandler onGestureEvent={gestureHandler}>
        <Animated.View
          style={[styles.container, { top: height * 0.7 }, animatedStyle]}
        >
          <SafeAreaView style={styles.wrapper}>
            <ContentConatiner>
              <Title>I am scroll sheet</Title>
              <HeroFlatList
                data={event}
                refreshControl={
                  <RefreshControl
                    enabled={true}
                    refreshing={loading}
                    onRefresh={fetchData}
                  />
                }
                keyExtractor={(_, index) => index.toString()}
                renderItem={({ item, index }) => {
                  const image = item?.description.images.map((img) => img.url);
                  const startDate = item?.event_dates?.starting_day;

                  return (
                    <EventContainer key={index}>
                      <EventImage
                        source={{
                          uri:
                            image[0] ||
                            "https://res.cloudinary.com/drewzxzgc/image/upload/v1631085536/zma1beozwbdc8zqwfhdu.jpg",
                        }}
                      />
                      <DescriptionContainer>
                        <Title ellipsizeMode="tail" numberOfLines={1}>
                          {item?.name?.en}
                        </Title>
                        <DescriptionText>
                          {item?.description?.intro ||
                            "No description available"}
                        </DescriptionText>
                        <DateText>{startDate}</DateText>
                      </DescriptionContainer>
                    </EventContainer>
                  );
                }}
              />
            </ContentConatiner>
          </SafeAreaView>
        </Animated.View>
      </PanGestureHandler>
    </>
  );
}

const styles = StyleSheet.create({
  container: {
    position: "absolute",
    top: 0,
    left: 0,
    right: 0,
    backgroundColor: "white",
    shadowColor: "black",
    shadowOffset: {
      height: -6,
      width: 0,
    },
    shadowOpacity: 0.1,
    shadowRadius: 5,
    borderTopEndRadius: 15,
    borderTopLeftRadius: 15,
  },
  mapStyle: {
    width: width,
    height: 800,
  },
});

const HeroFlatList = styled(FlatList).attrs({
  contentContainerStyle: {
    padding: 14,
    flexGrow: 1, // IT DOES NOT GROW
    paddingBottom: 2000, // BAD PRACTICE
  },
   height: 2000 /// BAD PRACTICE
})``; 


const ContentConatiner = styled.View`
  flex: 1;
  padding: 20px;
  background-color: #fff;
`;
const Title = styled.Text`
  font-size: 16px;
  font-weight: 700;
  margin-bottom: 5px;
`;

const DescriptionText = styled(Title)`
  font-size: 14px;
  opacity: 0.7;
`;

const DateText = styled(Title)`
  font-size: 14px;
  opacity: 0.8;
  color: #0099cc;
`;

const EventImage = styled.Image`
  width: 70px;
  height: 70px;
  border-radius: 70px;
  margin-right: 20px;
`;

const DescriptionContainer = styled.View`
  width: 200px;
`;

const EventContainer = styled(Animated.View)`
  flex-direction: row;
  padding: 20px;
  margin-bottom: 10px;
  border-radius: 20px;
  background-color: rgba(255, 255, 255, 0.8);
  shadow-color: #000;
  shadow-opacity: 0.3;
  shadow-radius: 20px;
  shadow-offset: 0 10px;
`;
5 Answers

Here is the updated version of your code. working fine on simulator

import React, { useState, useEffect, useRef } from "react";
import {
  StyleSheet,
  View,
  Dimensions,
  SafeAreaView,
  RefreshControl,
  Animated,
  LayoutAnimation,
} from "react-native";

import MapView from "react-native-maps";
import styled from "styled-components";

import { PanGestureHandler, FlatList } from "react-native-gesture-handler";

const { width, height } = Dimensions.get("screen");
const extendedHeight = height * 0.7;
const normalHeight = height * 0.4;
const bottomPadding = height * 0.15;

if (
  Platform.OS === "android" &&
  UIManager.setLayoutAnimationEnabledExperimental
) {
  UIManager.setLayoutAnimationEnabledExperimental(true);
}

const initialRegion = {
  latitudeDelta: 15,
  longitudeDelta: 15,
  latitude: 60.1098678,
  longitude: 24.7385084,
};

const api =
  "http://open-api.myhelsinki.fi/v1/events/?distance_filter=60.1699%2C24.9384%2C10&language_filter=en&limit=50";

export default function App() {
  const translateY = useRef(new Animated.Value(0)).current;
  const [flatListHeight, setFlatListHeight] = useState(extendedHeight);
  const [event, setEvent] = useState([]);
  const [loading, setLoading] = useState(false);

  // This is Fetch Dtata
  const fetchData = async () => {
    setLoading(true);
    try {
      const response = await fetch(api);
      const json = (await response.json()).data;
      setEvent(json);
    } catch (error) {
      console.log("erro", error);
    } finally {
      setLoading(false);
    }
  };
  useEffect(() => {
    fetchData();
  }, []);

  const bottomSheetIntropolate = translateY.interpolate({
    inputRange: [0, 1],
    outputRange: [-normalHeight, 0],
  });

  const animatedStyle = {
    transform: [
      {
        translateY: bottomSheetIntropolate,
      },
    ],
  };

  const animate = (bringDown) => {
    setFlatListHeight(extendedHeight);
    Animated.timing(translateY, {
      toValue: bringDown ? 1 : 0,
      duration: 500,
      useNativeDriver: true,
    }).start();
  };

  const onGestureEnd = (e) => {
    if (e.nativeEvent.translationY > 0) {
      LayoutAnimation.configureNext(LayoutAnimation.Presets.linear);
      setFlatListHeight(normalHeight);
    }
  };

  const gestureHandler = (e) => {
    if (e.nativeEvent.translationY > 0) {
      animate(true);
    } else if (e.nativeEvent.translationY < 0) {
      animate(false);
    }
  };

  const renderItem = ({ item, index }) => {
    const image = item?.description.images.map((img) => img.url);
    const startDate = item?.event_dates?.starting_day;
    return (
      <EventContainer key={index}>
        <EventImage
          source={{
            uri:
              image[0] ||
              "https://res.cloudinary.com/drewzxzgc/image/upload/v1631085536/zma1beozwbdc8zqwfhdu.jpg",
          }}
        />
        <DescriptionContainer>
          <Title ellipsizeMode="tail" numberOfLines={1}>
            {item?.name?.en}
          </Title>
          <DescriptionText>
            {item?.description?.intro || "No description available"}
          </DescriptionText>
          <DateText>{startDate}</DateText>
        </DescriptionContainer>
      </EventContainer>
    );
  };

  return (
    <>
      <MapView style={styles.mapStyle} initialRegion={initialRegion} />
      <PanGestureHandler onGestureEvent={gestureHandler} onEnded={onGestureEnd}>
        <Animated.View
          style={[styles.container, { top: height * 0.7 }, animatedStyle]}
        >
          <SafeAreaView style={styles.wrapper}>
            <ContentConatiner>
              <Title>I am scroll sheet</Title>
              <HeroFlatList
                style={{ height: flatListHeight }}
                data={event}
                ListFooterComponent={() => (
                  <View style={{ height: bottomPadding }} />
                )}
                refreshControl={
                  <RefreshControl
                    enabled={true}
                    refreshing={loading}
                    onRefresh={fetchData}
                  />
                }
                keyExtractor={(_, index) => index.toString()}
                renderItem={renderItem}
              />
            </ContentConatiner>
          </SafeAreaView>
        </Animated.View>
      </PanGestureHandler>
    </>
  );
}

const styles = StyleSheet.create({
  container: {
    position: "absolute",
    top: 0,
    left: 0,
    right: 0,
    backgroundColor: "white",
    shadowColor: "black",
    shadowOffset: {
      height: -6,
      width: 0,
    },
    shadowOpacity: 0.1,
    shadowRadius: 5,
    borderTopEndRadius: 15,
    borderTopLeftRadius: 15,
  },
  mapStyle: {
    width: width,
    height: 800,
  },
});

const HeroFlatList = styled(FlatList).attrs({
  contentContainerStyle: {
    padding: 14,
    flexGrow: 1, // IT DOES NOT GROW
  },
})``;

const ContentConatiner = styled.View`
  flex: 1;
  padding: 20px;
  background-color: #fff;
`;
const Title = styled.Text`
  font-size: 16px;
  font-weight: 700;
  margin-bottom: 5px;
`;

const DescriptionText = styled(Title)`
  font-size: 14px;
  opacity: 0.7;
`;

const DateText = styled(Title)`
  font-size: 14px;
  opacity: 0.8;
  color: #0099cc;
`;

const EventImage = styled.Image`
  width: 70px;
  height: 70px;
  border-radius: 70px;
  margin-right: 20px;
`;

const DescriptionContainer = styled.View`
  width: 200px;
`;

const EventContainer = styled(Animated.View)`
  flex-direction: row;
  padding: 20px;
  margin-bottom: 10px;
  border-radius: 20px;
  background-color: rgba(255, 255, 255, 0.8);
  shadow-color: #000;
  shadow-opacity: 0.3;
  shadow-radius: 20px;
  shadow-offset: 0 10px;
`;

Things I have updated in your code

  1. useRef instead of useState for holding animated value
  2. Replaced inline closure with const for renderItem (this can simplify the JSX and improve the performance)
  3. Unified your animation const to one.
  4. Utilised onEnded prop to decrease the height of FlatList gracefully with the help of LayoutAnimation
  5. Since you were accessing the width from Dimensions, the height can also be fetched from the same resource i.e. removed the hook
  6. updated the translateY logic calculation and Flatlist height to percentage base
  7. listFooterItem to provide some padding to last item
  8. A minor update to your fetch logic

If you're not against using react-native-reanimated, then I've made a minimally modified version of your code that should do exactly what you want.

I use Reanimated's v1 compatibility API, so you don't have to install the babel transpiler or anything. It should work as-is. https://snack.expo.dev/@switt/flatlist-scroll-reanimated

Example of working code

Reanimated is a better fit here, because React-Native's native Animated module cannot animate the top, bottom, width, height, etc. properties, it'd likely require setting useNativeDriver to false for what you're trying to achieve. That would lead to some performance drops/choppy frames during animation.

Here's your edited code just for convenience

import React, { useState, useEffect } from "react";
import {
  StyleSheet,
  Text,
  View,
  Dimensions,
  useWindowDimensions,
  SafeAreaView,
  RefreshControl,
  Animated,
  Button,
  FlatList,
  ScrollView
} from "react-native";

import MapView from "react-native-maps";
import styled from "styled-components";

import {
  PanGestureHandler,
  PanGestureHandlerGestureEvent,
  TouchableOpacity,
} from "react-native-gesture-handler";

import Reanimated, { EasingNode } from 'react-native-reanimated';

const { width } = Dimensions.get("screen");

const initialRegion = {
  latitudeDelta: 15,
  longitudeDelta: 15,
  latitude: 60.1098678,
  longitude: 24.7385084,
};

const api =
  "http://open-api.myhelsinki.fi/v1/events/?distance_filter=60.1699%2C24.9384%2C10&language_filter=en&limit=50";

export default function App() {
  const { height } = useWindowDimensions();
  const translateY = React.useRef(new Reanimated.Value(0)).current;

  const [event, setEvent] = useState([]);
  const [loading, setLoading] = useState(false);

  // This is Fetch Dtata
  const fetchData = async () => {
    try {
      setLoading(true);
      const response = await fetch(api);
      const data = await response.json();

      setEvent(data.data);
      setLoading(false);
    } catch (error) {
      console.log("erro", error);
    }
  };
  useEffect(() => {
    fetchData();
  }, []);

  // Animation logic
  const bringUpActionSheet = () => {
    Reanimated.timing(translateY, {
      toValue: 0,
      duration: 500,
      useNativeDriver: true,
      easing: EasingNode.inOut(EasingNode.ease)
    }).start();
  };
  const closeDownBottomSheet = () => {
    Reanimated.timing(translateY, {
      toValue: 1,
      duration: 500,
      useNativeDriver: true,
      easing: EasingNode.inOut(EasingNode.ease)
    }).start();
  };
  
  const bottomSheetTop = translateY.interpolate({
    inputRange: [0, 1],
    outputRange: [height * 0.7 - height / 2.4 + 50, height * 0.7]
  });
  const animatedStyle = {
    top: bottomSheetTop,
    bottom: 0
  };

  const gestureHandler = (e: PanGestureHandlerGestureEvent) => {
    if (e.nativeEvent.translationY > 0) {
      closeDownBottomSheet();
    } else if (e.nativeEvent.translationY < 0) {
      bringUpActionSheet();
    }
  };

  return (
    <>
      <MapView style={styles.mapStyle} initialRegion={initialRegion} />
      <PanGestureHandler onGestureEvent={gestureHandler}>
        <Reanimated.View
          style={[styles.container, { top: height * 0.7 }, animatedStyle]}
        >
              <Title>I am scroll sheet</Title>
              <HeroFlatList
                data={event}
                refreshControl={
                  <RefreshControl
                    enabled={true}
                    refreshing={loading}
                    onRefresh={fetchData}
                  />
                }
                keyExtractor={(_, index) => index.toString()}
                renderItem={({ item, index }) => {
                  const image = item?.description.images.map((img) => img.url);
                  const startDate = item?.event_dates?.starting_day;
                  return (
                    <EventContainer key={index}>
                      <EventImage
                        source={{
                          uri:
                            image[0] ||
                            "https://res.cloudinary.com/drewzxzgc/image/upload/v1631085536/zma1beozwbdc8zqwfhdu.jpg",
                        }}
                      />
                      <DescriptionContainer>
                        <Title ellipsizeMode="tail" numberOfLines={1}>
                          {item?.name?.en}
                        </Title>
                        <DescriptionText>
                          {item?.description?.intro ||
                            "No description available"}
                        </DescriptionText>
                        <DateText>{startDate}</DateText>
                      </DescriptionContainer>
                    </EventContainer>
                  );
                }}
              />
        </Reanimated.View>
      </PanGestureHandler>
    </>
  );
}

const styles = StyleSheet.create({
  container: {
    position: "absolute",
    top: 0,
    left: 0,
    right: 0,
    backgroundColor: "white",
    shadowColor: "black",
    shadowOffset: {
      height: -6,
      width: 0,
    },
    shadowOpacity: 0.1,
    shadowRadius: 5,
    borderTopEndRadius: 15,
    borderTopLeftRadius: 15,
  },
  mapStyle: {
    width: width,
    height: 800,
  },
});

const HeroFlatList = styled(FlatList).attrs({
  contentContainerStyle: {
  paddingBottom: 50
  },
  // height:510,
  // flex:1
})``; 



const Title = styled.Text`
  font-size: 16px;
  font-weight: 700;
  margin-bottom: 5px;
`;

const DescriptionText = styled(Title)`
  font-size: 14px;
  opacity: 0.7;
`;

const DateText = styled(Title)`
  font-size: 14px;
  opacity: 0.8;
  color: #0099cc;
`;

const EventImage = styled.Image`
  width: 70px;
  height: 70px;
  border-radius: 70px;
  margin-right: 20px;
`;

const DescriptionContainer = styled.View`
  width: 200px;
`;

const EventContainer = styled(Animated.View)`
  flex-direction: row;
  padding: 20px;
  margin-bottom: 10px;
  border-radius: 20px;
  background-color: rgba(255, 255, 255, 0.8);
  shadow-color: #000;
  shadow-opacity: 0.3;
  shadow-radius: 20px;
  shadow-offset: 0 10px;
`;

Finally I found the solution what I wanted. Thank you Stack-overflow community. Without your help I could not able to do that.

import React, { useState, useEffect } from "react";
import {
  StyleSheet,
  Text,
  View,
  Dimensions,
  useWindowDimensions,
  SafeAreaView,
  RefreshControl,
  Animated,
  Platform
} from "react-native";

import MapView from "react-native-maps";
import styled from "styled-components";

import {
  PanGestureHandler,
  PanGestureHandlerGestureEvent,
  TouchableOpacity,
  FlatList
} from "react-native-gesture-handler";

const { width } = Dimensions.get("screen");
const IPHONE_DEVICE_START_HEIGHT = Platform.OS === 'ios' ? 0.4 : 0.6;
const initialRegion = {
  latitudeDelta: 15,
  longitudeDelta: 15,
  latitude: 60.1098678,
  longitude: 24.7385084,
};

const api =
  "http://open-api.myhelsinki.fi/v1/events/?distance_filter=60.1699%2C24.9384%2C10&language_filter=en&limit=50";

export default function App() {
  const { height } = useWindowDimensions();
  const [translateY] = useState(new Animated.Value(0));

  const [event, setEvent] = useState([]);
  const [loading, setLoading] = useState(false);

  // This is Fetch Dtata
  const fetchData = async () => {
    try {
      setLoading(true);
      const response = await fetch(api);
      const data = await response.json();

      setEvent(data.data);
      setLoading(false);
    } catch (error) {
      console.log("erro", error);
    }
  };
  useEffect(() => {
    fetchData();
  }, []);

  // Animation logic
  const bringUpActionSheet = () => {
    Animated.timing(translateY, {
      toValue: 0,
      duration: 500,
      useNativeDriver: false,
    }).start();
  };

  const closeDownBottomSheet = () => {
    Animated.timing(translateY, {
      toValue: 1,
      duration: 500,
      useNativeDriver: false,
    }).start();
  };

  const bottomSheetIntropolate = translateY.interpolate({
   inputRange: [0, 1],
    outputRange: [
      height * 0.5 - height / 2.4 + IPHONE_DEVICE_START_HEIGHT,
      height * IPHONE_DEVICE_START_HEIGHT,
    ],
    extrapolate: 'clamp',
  });

  const animatedStyle = {
    top: bottomSheetIntropolate,
    bottom: 0,
  };

  const gestureHandler = (e: PanGestureHandlerGestureEvent) => {
    if (e.nativeEvent.translationY > 0) {
      closeDownBottomSheet();
    } else if (e.nativeEvent.translationY < 0) {
      bringUpActionSheet();
    }
  };

  return (
    <>
      <MapView style={styles.mapStyle} initialRegion={initialRegion} />
      <PanGestureHandler onGestureEvent={gestureHandler}>
        <Animated.View
          style={[styles.container, { top: height * 0.7 }, animatedStyle]}
        >
              <Title>I am scroll sheet</Title>
              <HeroFlatList
                data={event}
                refreshControl={
                  <RefreshControl
                    enabled={true}
                    refreshing={loading}
                    onRefresh={fetchData}
                  />
                }
                keyExtractor={(_, index) => index.toString()}
                renderItem={({ item, index }) => {
                  const image = item?.description.images.map((img) => img.url);
                  const startDate = item?.event_dates?.starting_day;
                  return (
                    <EventContainer key={index}>
                      <EventImage
                        source={{
                          uri:
                            image[0] ||
                            "https://res.cloudinary.com/drewzxzgc/image/upload/v1631085536/zma1beozwbdc8zqwfhdu.jpg",
                        }}
                      />
                      <DescriptionContainer>
                        <Title ellipsizeMode="tail" numberOfLines={1}>
                          {item?.name?.en}
                        </Title>
                        <DescriptionText>
                          {item?.description?.intro ||
                            "No description available"}
                        </DescriptionText>
                        <DateText>{startDate}</DateText>
                      </DescriptionContainer>
                    </EventContainer>
                  );
                }}
              />
        
        </Animated.View>
      </PanGestureHandler>
    </>
  );
}

const styles = StyleSheet.create({
  container: {
    position: "absolute",
    top: 0,
    left: 0,
    right: 0,
    backgroundColor: "white",
    shadowColor: "black",
    shadowOffset: {
      height: -6,
      width: 0,
    },
    shadowOpacity: 0.1,
    shadowRadius: 5,
    borderTopEndRadius: 15,
    borderTopLeftRadius: 15,
  },
  mapStyle: {
    width: width,
    height: 800,
  },
});

const HeroFlatList = styled(FlatList).attrs({
  contentContainerStyle: {
   flexGrow:1
  },

})``; 



const Title = styled.Text`
  font-size: 16px;
  font-weight: 700;
  margin-bottom: 5px;
`;

const DescriptionText = styled(Title)`
  font-size: 14px;
  opacity: 0.7;
`;

const DateText = styled(Title)`
  font-size: 14px;
  opacity: 0.8;
  color: #0099cc;
`;

const EventImage = styled.Image`
  width: 70px;
  height: 70px;
  border-radius: 70px;
  margin-right: 20px;
`;

const DescriptionContainer = styled.View`
  width: 200px;
`;

const EventContainer = styled(Animated.View)`
  flex-direction: row;
  padding: 20px;
  margin-bottom: 10px;
  border-radius: 20px;
  background-color: rgba(255, 255, 255, 0.8);
  shadow-color: #000;
  shadow-opacity: 0.3;
  shadow-radius: 20px;
  shadow-offset: 0 10px;
`;

Looking at your code,

You need to change

import {
  PanGestureHandler,
  PanGestureHandlerGestureEvent,
  TouchableOpacity,
  FlatList as GFlatList
} from "react-native-gesture-handler";

Import FlatList from react-native-gesture-handler and update your code as below,

const HeroFlatList = styled(GFlatList).attrs({
  contentContainerStyle: {
  paddingBottom: 50
  },
  height:510,
})``; 

I have tested this on your code and it works. Please check.

keep HeroFlatList in scrollView.

<ScrollView>
  <HeroFlatList
    data={event}
    refreshControl={
    <RefreshControl
      enabled={true}
      refreshing={loading}
      onRefresh={fetchData}
     />
    }
    keyExtractor={(_, index) => index.toString()}
    renderItem={({ item, index }) => {
      const image = item?.description.images.map((img) => img.url);
      const startDate = item?.event_dates?.starting_day;
      return (
        <EventContainer key={index}>
          <EventImage
            source={{
              uri:
                image[0] || "https://res.cloudinary.com/drewzxzgc/image/upload/v1631085536/zma1beozwbdc8zqwfhdu.jpg",
             }}
          />
          <DescriptionContainer>
             <Title ellipsizeMode="tail" numberOfLines={1}>
                {item?.name?.en}
             </Title>
             <DescriptionText>
                {item?.description?.intro || "No description available"}
             </DescriptionText>
             <DateText>{startDate}</DateText>
          </DescriptionContainer>
       </EventContainer>
     );
   }}
 />
</ScrollView>
Related