Search code examples
react-nativeflexboxscrollviewpaddingreact-native-flatlist

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


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;
`;

Solution

  • 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;
    `;