I tried to display my posts using Flatlist (gensture-handler) + setup infinite scroll to load new posts. when i scrolling the flatlist, i have an empty space (idk why). as you can see on screenshot, scrollview thumb in the center of scrollview, but there empty space
NEW POST COMPONENT
import { t } from 'i18next';
import { useEffect, useState, useCallback } from 'react';
import { View, FlatList, Text } from 'react-native';
import { api } from '../../../../services/Api';
import { IPost } from '../../../../types';
import Button from '../../../common/Button';
import CardSkeleton from '../../../common/CardSkeleton';
import { ScreenContainer } from '../../../containers/ScreenContainer';
import { PostCard } from '../../Post/PostCard/PostCard';
import HomeSection from '../molecules/HomeSection/HomeSection';
import { twMerge } from 'tailwind-merge';
const MoreNewPosts = () => {
const [search, { data, isLoading, isSuccess }] =
api.useSearchByQueryMutation();
const [currentPage, setCurrentPage] = useState(1);
const [allPosts, setAllPosts] = useState<IPost[]>([]);
useEffect(() => {
loadPosts(1);
}, []);
const loadPosts = (page: number) => {
search({
page,
sort_by: 'newest',
}).then((response) => {
if (response && 'data' in response) {
setAllPosts((prevPosts) => {
const newPosts = response.data.posts.filter(
(post) => !prevPosts.some((prevPost) => prevPost.id === post.id),
);
return [...prevPosts, ...newPosts];
});
}
});
};
const handleLoadMore = () => {
const nextPage = currentPage + 1;
setCurrentPage(nextPage);
loadPosts(nextPage);
};
const renderItem = ({ item, index }: { item: IPost; index: number }) => (
<View key={item.id} className={twMerge(index % 2 === 0 ? 'mr-2' : 'ml-2')}>
<PostCard post={item} isLoading={isLoading} />
</View>
);
const renderFooter = () => {
if (isLoading) {
return Array.from({ length: 10 }).map((_, index) => (
<CardSkeleton key={index} isLoading={isLoading} />
));
}
return null;
};
return (
<ScreenContainer
edges={['left', 'right', 'bottom']}
className="flex-1 bg-white"
>
<HomeSection
label={t('translation:dashboard.fresh_posts.message')}
elementsAlign="end"
>
{allPosts.length ? (
<FlatList
data={allPosts}
numColumns={2}
renderItem={renderItem}
keyExtractor={(item) => item.id.toString()}
onEndReached={handleLoadMore}
onEndReachedThreshold={0.5}
ListFooterComponent={renderFooter}
removeClippedSubviews
/>
) : null}
</HomeSection>
</ScreenContainer>
);
};
export default MoreNewPosts;
POST CARD COMPONENT
import { useNavigation } from '@react-navigation/native';
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import { t } from 'i18next';
import { memo, useState } from 'react';
import { Pressable, Text, View, ViewStyle } from 'react-native';
import { Image } from 'react-native';
import Carousel from 'react-native-reanimated-carousel';
import { twMerge } from 'tailwind-merge';
import { useAppSelector } from '../../../../hooks/useActions';
import { getShortProductName } from '../../../../lib/text';
import { IPost } from '../../../../types';
import {
CompositeRootStackNavigationProp,
RootStackParamList,
} from '../../../../types/types';
import { getPostPrice } from '../../../../utils/get';
import LikeComponent from '../LikeComponent/LikeComponent';
import DefaultAvatar from '../../../common/DefaultAvatar';
import Chip from '../../../common/Chip';
export const PostCard = memo(
({
post,
className,
style,
isLoading,
shouldResize = true,
isFilter,
}: {
post: IPost;
className?: string;
style?: ViewStyle;
isLoading?: boolean;
shouldResize?: boolean;
isFilter?: boolean;
}) => {
const navigation = useNavigation<CompositeRootStackNavigationProp>();
const { activeCurrency, isAuthorized } = useAppSelector(
(state) => state.authReducer,
);
return (
<Pressable
onPress={() => {
navigation.push('Tabs', {
screen: 'HomeStack',
params: {
screen: 'Post',
params: { id: post.id },
},
});
}}
className={twMerge(
'relative rounded-[10px] rounded-t-[20px] bg-[#f5f5f5] min-w-[170px] max-w-[50%] flex-1',
className,
)}
style={{
height: 250,
...style,
}}
>
<View
style={{ backgroundColor: post.used ? '#F8D7A9' : '#99D6A6' }}
className="absolute left-2 top-2 justify-center items-center rounded-[20px] px-2 py-[5px] z-50"
>
<Text
style={{
color: post.used ? '#D96804' : '#017B1C',
}}
className="text-[12px] font-semibold"
>
{post.used
? t('translation:product_card.used.message')
: t('translation:product_card.new.message')}
</Text>
</View>
{isAuthorized && <LikeComponent post={post} />}
<Image
height={150}
className="self-center w-full rounded-[20px] rounded-b-[0px] border-2 border-[#f5f5f5]"
source={
post.images_variants.length
? { uri: post.images_variants[0].original }
: require('../../../../../assets/default_image.png')
}
/>
<View className="py-[3px] w-full px-[7px]">
<View className="flex-row justify-between items-center">
<Text className=" text-[18px] font-medium">
{post.price === null
? t('translation:product_card.discussed.message')
: post.price == 0.0
? t('translation:product_card.free.message')
: getPostPrice(post, activeCurrency)}
</Text>
<Text className="text-[12px] font-normal">
{post.user_location?.name}
</Text>
</View>
<Text
numberOfLines={2}
ellipsizeMode="tail"
className="text-[14px] mb-2 min-h-[40px] max-h-[40px] font-normal"
>
{post.title}
</Text>
<View className="flex-row py-1 items-center">
{post.user.avatar ? (
<Image
className="rounded-full w-4 h-4"
source={{ uri: post.user.avatar }}
/>
) : (
<DefaultAvatar size={16} username={post.user.username} />
)}
<Text
className="text-[12px] font-normal ml-1"
numberOfLines={1}
ellipsizeMode="tail"
>
{post.user.username}
</Text>
</View>
</View>
</Pressable>
);
},
(prevProps, nextProps) => {
return prevProps.post === nextProps.post;
},
);
I tried different keys, optimize the post card. before that I just had a “load more” button and everything worked fine (using scrollview). however when I tried infinite scroll, everything broke