onEndReached function calls several times in FlatList(react-native) - react-native

In my code, for the first time, loadAlbums method runs correctly. Why I scroll down retrieveMore function calls several times.
const PAGE_SIZE = 15;
const App = () => {
const [albums, setAlbums] = useState([]);
const [page, setPage] = useState(1);
const loadAlbums = async () => {
const response = await axios.get(`http://xxx:3000/posts/${PAGE_SIZE}/${page}`);
setAlbums([...albums, ...response.data]);
setPage(page + 1)
}
useEffect(() => {
loadAlbums();
}, [])
const renderRowItem = ({ item }) => {
return (
<View style={styles.item}>
<Text>{item.id}</Text>
<Text>{item.body}</Text>
</View>
)
}
const retrieveMore = useCallback(() => {
loadAlbums(); // this line calls several times
})
return (
<View style={styles.screen}>
<FlatList
data={albums}
renderItem={renderRowItem}
keyExtractor={(item, index) => index.toString()}
onEndReached={retrieveMore}
onEndReachedThreshold={0.5}
/>
</View>
)
}

Just Reduce threshold
onEndReachedThreshold={0.5} to onEndReachedThreshold={0.2}
Currently what's happening here every time you reach 50% of the screen the api will be invoked.

Related

how to prevent keep getting duplicated result from API with Flatlist react native?

im trying to get repos in order of stars from the API, but as i scroll, i keep getting random duplicates :
export default function App() {
const [repos, setRepos] = useState([]);
const [page, setPage] = useState(1);
const [loading, setLoading] = useState(true);
useEffect(() => {
const getUsers = async () => {
const res = await axios.get(
`https://api.github.com/search/repositories?q=created:>2020-01-01&sort=stars&order=desc&page=${page}`
);
const data = await res.data;
// setRepos([...repos, ...data.items]);
setRepos([...repos, ...data.items]);
setLoading(false);
};
getUsers();
}, [page]);
const scrollToEnd = () => {
if (loading === false) {
setPage(page + 1);
setLoading(true);
}
console.log("page", page);
};
const renderItem = ({ item }) => (
<Text className="boxed">
Stars: {item.stargazers_count} Id: {item.id}
</Text>
);
return (
<SafeAreaView style={styles.screen}>
<View style={styles.container}>
<FlatList
data={repos}
renderItem={renderItem}
keyExtractor={(item) => item.id}
onEndReached={scrollToEnd}
showsVerticalScrollIndicator={false}
/>
{loading && <Text className="loading">...loading</Text>}
</View>
<StatusBar style="auto" />
</SafeAreaView>
);
}
You should avoid duplicate data before set it to state, update your getUsers function with this,
useEffect(() => {
const getUsers = async () => {
const res = await Axios.get(
`https://api.github.com/search/repositories?q=created:>2020-01-01&sort=stars&order=desc&page=${page}`,
);
const data = await res.data;
let arrRepos = [...repos, ...data.items];
let uniq = new Set(arrRepos.map((objRepo) => JSON.stringify(objRepo)));
arrRepos = Array.from(uniq).map((objRepo) => JSON.parse(objRepo));
setRepos(arrRepos);
setLoading(false);
};
getUsers();
}, [page]);
I think you got too much going on in your useEffect function, maybe missing a closing }. Try this:
function App() {
const [repos, setRepos] = useState([]);
const [page, setPage] = useState(1);
const [loading, setLoading] = useState(true);
// This will trigger when page changes:
useEffect(() => {
getUsers();
}, [page]);
const getUsers = async () => {
const res = await axios.get(
`https://api.github.com/search/repositories?q=created:>2020-01-01&sort=stars&order=desc&page=${page}`
);
const data = await res.data;
// setRepos([...repos, ...data.items]);
setRepos([...repos, ...data.items]);
setLoading(false);
};
const scrollToEnd = () => {
if (loading === false) {
setPage(page + 1);
setLoading(true);
}
console.log("page", page);
};
const renderItem = ({ item }) => (
<Text className="boxed">
Stars: {item.stargazers_count} Id: {item.id}
</Text>
);
return (
<SafeAreaView style={styles.screen}>
<View style={styles.container}>
<FlatList
data={repos}
renderItem={renderItem}
keyExtractor={(item) => item.id}
onEndReached={scrollToEnd}
showsVerticalScrollIndicator={false}
/>
{loading && <Text className="loading">...loading</Text>}
</View>
<StatusBar style="auto" />
</SafeAreaView>
);
}
export default App;

How to show ActivityIndicator at the bottom of the list in FlatList?

I am trying to display ActivityIndicator when the list reaches the bottom. However, currently it loads without showing the spinner. Am I doing this wrongly?
And sometimes when it shows the ActivityIndicator , it will call loadShops twice resulting in calling the API twice.
const dispatch = useDispatch();
const shops = useSelector((state) => state.shops.availableShops);
const [shopLimit, setShopLimit] = useState(10);
const [skip, setSkip] = useState(0);
const [isLoading, setIsLoading] = useState(false);
const loadShops = async () => {
setIsLoading(true);
try {
await dispatch(shopsActions.getShopsList(skip, shopLimit));
} catch (error) {
throw error;
}
setIsLoading(false);
};
useEffect(() => {
console.log("useEffect called!!");
if (shopLimit <= 30) {
loadShops();
}
}, [shopLimit]);
return (
<View style={styles.container}>
{console.log("rendering, ", `isLoadingState: ${isLoading}`)}
<SearchBarCmp
containerStyle={styles.containerStyle}
inputContainerStyle={styles.inputContainerStyle}
placeholder={"Search by shop name or location..."}
/>
<FlatList
data={shops}
renderItem={(shop) => (
<ShopItem
imageUrl={shop.item.url}
shopName={shop.item.name}
pickupHour={shop.item.pickup_hour}
originalPrice={shop.item.original_price}
discountedPrice={shop.item.discounted_price}
/>
)}
keyExtractor={(item, index) => index.toString()}
onEndReached={() => {
setShopLimit((prevState) => prevState + 10);
}}
onEndReachedThreshold={0.01}
scrollEventThrottle={150}
ListFooterComponent={() => {
if (isLoading) {
return (
<View style={styles.footerContainer}>
<ActivityIndicator size="large" color="#0000ff" />
</View>
);
} else {
return null
}
}}
/>
</View>
);
use setIsLoading(false); in finally
try{}
catch(error){}
finally { setIsLoading(false); }
Your loader is set to false before the try catch completes

Show ActivityIndicator in React Native?

I have a function to fetch items from an API that is inside UseEffect. And i'm looking to call this function every time the status of the selectedItem or the items changes and show an ActivityIndicator before the function returns the result. The ActivityIndicator appears when the items are uploading but not when the status of the selectedItem changes ?
I have my code like this :
export default () => {
const [items, setItems] = useState();
const [selectedItem, setSelectedItem] = useState(null);
const [isLoading, setLoading] = useState(true);
const getItems = () => {
get('/api/items').then((rep) => {
setItems(rep);
setLoading(false);
}
});
};
useEffect(() => {
getItems();
}, [selectedItem.status]);
return (
<SafeAreaView style={styles.container}>
{isLoading ? (
<View style={[styles.spinnerContainer, styles.horizontal]}>
<ActivityIndicator />
</View>
) : ((items !== [])
&& (
<SectionList
stickySectionHeadersEnabled={false}
style={{ paddingHorizontal: 20, }}
sections={items}
refreshing={isLoading}
keyExtractor={(item, index) => item + index}
...
/>
))}
</SafeAreaView>
);
};
You can try setLoading(true) inside getItems
const getItems = () => {
setLoading(true);
get('/api/items').then((rep) => {
setItems(rep);
setLoading(false);
});
};

React-Native FlatList item clickable with data to another screen

I'm trying to access a screen when you click on an item in my flatlist by passing the date I retrieved from the firebase before, I've tried several things without success so I come to you.
Basically when I click on one of the elements -> A screen with details should appear.
export default function Notifications() {
const dbh = firebase.firestore();
const [loading, setLoading] = useState(true); // Set loading to true on component mount
const [deliveries, setDeliveries] = useState([]); // Initial empty array of users
useEffect(() => {
const subscriber = dbh
.collection("deliveries")
.onSnapshot((querySnapshot) => {
const deliveries = [];
querySnapshot.forEach((documentSnapshot) => {
deliveries.push({
...documentSnapshot.data(),
key: documentSnapshot.id,
});
});
setDeliveries(deliveries);
setLoading(false);
});
// Unsubscribe from events when no longer in use
return () => subscriber();
}, []);
if (loading) {
return <ActivityIndicator />;
}
return (
<FlatList
style={{ flex: 1 }}
data={deliveries}
renderItem={({ item }) => (
<TouchableOpacity
onPress={() => { * HERE I NEED TO PASS DATA AND SHOW AN ANOTHER SCREEN FOR DETAILS * }}>
<View style={styles.container}>
<Text>DATE: {item.when}</Text>
<Text>ZIP DONATEUR: {item.zip_donator}</Text>
<Text>ZIP BENEFICIAIRE: {item.zip_tob_deliv}</Text>
</View>
</TouchableOpacity>
)}
/>
);
}
EDIT: Small precision this screen is located in a Tab.Navigator
you can pass params in navigation,
export default function Notifications(props) {
const { navigation } = props
const dbh = firebase.firestore();
const [loading, setLoading] = useState(true); // Set loading to true on component mount
const [deliveries, setDeliveries] = useState([]); // Initial empty array of users
useEffect(() => {
const subscriber = dbh
.collection("deliveries")
.onSnapshot((querySnapshot) => {
const deliveries = [];
querySnapshot.forEach((documentSnapshot) => {
deliveries.push({
...documentSnapshot.data(),
key: documentSnapshot.id,
});
});
setDeliveries(deliveries);
setLoading(false);
});
// Unsubscribe from events when no longer in use
return () => subscriber();
}, []);
if (loading) {
return <ActivityIndicator />;
}
return (
<FlatList
style={{ flex: 1 }}
data={deliveries}
renderItem={({ item }) => (
<TouchableOpacity
onPress={() => {
navigation.navigate('screenName', {
//pass params here
})
}}>
<View style={styles.container}>
<Text>DATE: {item.when}</Text>
<Text>ZIP DONATEUR: {item.zip_donator}</Text>
<Text>ZIP BENEFICIAIRE: {item.zip_tob_deliv}</Text>
</View>
</TouchableOpacity>
)}
/>
);
}
you can access params in the navigated screen by props.route.params

React Native scroll to refresh is not working properly with pagination

i am new to react native please bear with me. I currently have a flatlist in my react native project which fetch's posts from my backend using an api. I have success implemented pagination on my backend and in my app when i scroll to the end it adds the newly fetched posts to the bottom of the current posts shown.
My problem is when i scroll to refresh, i only want the posts of page 0 to get shown.
What is happening is the posts on page 0 get added to the bottom of the posts instead of showing these posts only.
So for example, when i load my app, posts on page 0 get rendered and when i scroll to refresh instead of just showing posts on page 0, the posts on page 0 get added to the bottom of the posts on page 0 and therefore i get an error that id is duplicated.
The required action i am trying to obtain is when i scroll to refresh i just want the posts on page 0 to show.
Here is my code:
function PostsScreen({ navigation }) {
const [posts, setPosts] = useState([]);
const [error, setError] = useState(false);
const [loading, setLoading] = useState(false);
const loadPosts = async () => {
setLoading(true);
const response = await postsApi.getPosts(page);
setLoading(false);
if (!response.ok) return setError(true);
setError(false);
setPosts([...posts,...response.data]);
};
const[page,setPage]=useState(0);
useEffect(() => {
loadPosts(page);
}, [page]);
const handleLoadMore = ()=>{
console.log('loadmore')
setPage(page+1);
}
const [refreshing, setRefreshing] = useState(false);
return (
<>
<ActivityIndicator visible={loading} />
<Screen style={styles.screen}>
<FlatList
data={posts} // to have all the data
keyExtractor={(post) => post.id.toString()}
renderItem={({ item,index }) => (
<Card
title={item.title}
subTitle={item.subTitle}
onPress={() => navigation.navigate(routes.POST_DETAILS, {post:item,index})}
/>
)}
refreshing={refreshing}
onRefresh={() => {
loadPosts(0); // I think the problem is here.//
setPage(0);
}}
onEndReached={handleLoadMore}
onEndReachedThreshold={0}
initialNumToRender={10}
/>
</Screen>
</>
);
}
Thank you in advance.
From what I read, I believe you want to implement pagination in your application and you have that in your code as well. Now, when you try refreshing your list, you just want to set the list to the initial 10 items. if so,
function PostsScreen({ navigation }) {
const [posts, setPosts] = useState([]);
const [error, setError] = useState(false);
const [loading, setLoading] = useState(false);
const [page, setPage] = useState(0);
const [refreshing, setRefreshing] = useState(false);
const loadPosts = async () => {
setLoading(true);
const response = await postsApi.getPosts(page);
setLoading(false);
if (refreshing)
setRefreshing(false);
if (!response.ok) return setError(true);
setError(false);
if (page == 0)
setPosts(response.data)
else
setPosts([...posts, ...response.data]);
};
useEffect(() => {
loadPosts();
}, [page]);
const handleLoadMore = () => {
setPage(page + 1);
};
const onRefresh = () => {
setRefreshing(true);
setPage(0);
};
return (
<>
<ActivityIndicator visible={loading} />
<Screen style={styles.screen}>
<FlatList
data={posts}
renderItem={({ item, index }) => (
<Card
title={item.title}
subTitle={item.subTitle}
onPress={() => navigation.navigate(routes.POST_DETAILS, { post: item, index })}
/>
)}
refreshing={refreshing}
onRefresh={onRefresh}
onEndReached={handleLoadMore}
onEndReachedThreshold={0.05}
keyExtractor={(post) => post.id.toString()}
/>
</Screen>
</>
);
}
This should fix your problem.
const loadPosts = async () => {
setLoading(true);
const response = await postsApi.getPosts(page);
setLoading(false);
if (!response.ok) return setError(true);
setError(false);
if(page=== 0){
setPosts(response.data);
}else{
setPosts([...posts,...response.data]);
}
};
//...
onRefresh={() => {
setPage(0);
loadPosts(); // loadPosts takes no argument.
}
}
//...
const handleLoadMore = ()=>{
console.log('loadmore')
setPage(page+1);
loadPosts();
}