React hooks FlatList pagination - javascript

I am trying to let the FlatList get 20 posts from Firestore and render 20. when the end is reached I would like to call the getPosts method to get the next 20 posts which means I will have to have a way to save the last known cursor. This is what I was trying to do when converting class component to hooks.
Please can someone help me , no one answered my last question about this
const Posts = (props) => {
//How to get 20 posts from firebase and then render 20 more when the end is reached
const [allPosts, setAllPosts] = useState();
const [loading, setLoading] = useState(true)
const [isRefreshing, setRefreshing] = useState(false);
useEffect(() => {
getPosts();
}, []);
const getPosts = async () => {
try {
var all = [];
const unsubscribe = await firebase
.firestore()
.collection("Posts")
.orderBy("timestamp",'desc')
.get()
.then((querySnapshot) => {
querySnapshot.docs.forEach((doc) => {
all.push(doc.data());
});
setLoading(false);
});
setAllPosts(all);
if(currentUser === null){
unsubscribe()
}
} catch (err) {
setLoading(false);
}
};
const onRefresh = useCallback(() => {
setRefreshing(true);
getPosts()
.then(() => {
setRefreshing(false);
})
.catch((error) => {
setRefreshing(false); // false isRefreshing flag for disable pull to refresh
Alert.alert("An error occured", "Please try again later");
});
}, []);
return (
<FlatList
data={allRecipes}
refreshControl={
<RefreshControl
refreshing={isRefreshing}
onRefresh={onRefresh}
/>
}
initialNumToRender={20}
keyExtractor={(item, index) => item.postId}
renderItem={renderItem}
/>
);
}

const Posts = () =>{
const [posts, setPosts] = useState();
const [data, setData] = useState();
const addPosts = posts => {
setData({...data,...posts})
// `setData` is async , use posts directly
setPosts(Object.values(posts).sort((a, b) => a.timestamp < b.timestamp))
};
}

You need to add a scroll event listener here
something like:
const Posts = (props) => {
useEffect(() => {
window.addEventListener('scroll', () => {
if (window.scrollY >= (document.body.offsetHeight + window.innerHeight)) {
// fetch more posts here
}
});
});
// ...rest of the codes
}

Related

Search function now working in React photo gallary

Working on a small application that takes a pexels api and displays photos dynamically. When I send the search request for my api to fectch based on the new params, it does actually update the page with new photos but not the ones based on the params. I though I got the search function correct, maybe it's cause I'm not using it in a useEffect? But if I did use it in a useEffect, I wouldn't be able to set it on the onClick handle. I tried to console.log the query I was getting from the onChange but it doesn't seem like it's getting the result. What am I doing wrong?
import { useState, useEffect } from 'react'
import pexelsApi from './components/pexelsApi'
import './App.css'
const App = () => {
const [images, setImages] = useState([]);
const [loading, setLoading] = useState(false);
const [nextPage, setNextPage] = useState(1);
const [perPage, setPerPage] = useState(25);
const [query, setQuery] = useState('');
const [error, setError] = useState('');
useEffect(() => {
const getImages = async () => {
setLoading(true);
await pexelsApi.get(`/v1/curated?page=${nextPage}&per_page=${perPage}`)
.then(res => {
setImages([...images, ...res.data.photos]);
setLoading(false);
}).catch(er => {
if (er.response) {
const error = er.response.status === 404 ? 'Page not found' : 'Something wrong has happened';
setError(error);
setLoading(false);
console.log(error);
}
});
}
getImages();
}, [nextPage, perPage]);
const handleLoadMoreClick = () => setNextPage(nextPage + 1)
const search = async (query) => {
setLoading(true);
await pexelsApi.get(`/v1/search?query=${query}&per_page=${perPage}`)
.then(res => {
setImages([...res.data.photos]);
console.log(res.data)
setLoading(false);
console.log(query)
})
}
if (!images) {
return <div>Loading</div>
}
return (
<>
<div>
<input type='text' onChange={(event) => setQuery(event.target.value)} />
<button onClick={search}>Search</button>
</div>
<div className='image-grid'>
{images.map((image) => <img key={image.id} src={image.src.original} alt={image.alt} />)}
</div>
<div className='load'>
{nextPage && <button onClick={handleLoadMoreClick}>Load More Photos</button>}
</div>
</>
)
};
export default App
import axios from 'axios';
export default axios.create({
baseURL: `https://api.pexels.com`,
headers: {
Authorization: process.env.REACT_APP_API_KEY
}
});
Your main issue is that you've set query as an argument to your search function but never pass anything. You can just remove the arg to have it use the query state instead but you'll then need to handle pagination...
// Helper functions
const getCuratedImages = () =>
pexelsApi.get("/v1/curated", {
params: {
page: nextPage,
per_page: perPage
}
}).then(r => r.data.photos)
const getSearchImages = (page = nextPage) =>
pexelsApi.get("/v1/search", {
params: {
query,
page,
per_page: perPage
}
}).then(r => r.data.photos)
// initial render effect
useEffect(() => {
setLoading(true)
getCuratedImages().then(photos => {
setImages(photos)
setLoading(false)
})
}, [])
// search onClick handler
const search = async () => {
setNextPage(1)
setLoading(true)
setImages(await getSearchImages(1)) // directly load page 1
setLoading(false)
}
// handle pagination parameter changes
useEffect(() => {
// only action for subsequent pages
if (nextPage > 1) {
setLoading(true)
const promise = query
? getSearchImages()
: getCuratedImages()
promise.then(photos => {
setImages([...images, ...photos])
setLoading(false)
})
}
}, [ nextPage ])
The reason I'm passing in page = 1 in the search function is because the setNextPage(1) won't have completed for that first page load.

Firebase realtime not working correctly on React

I'm creating lobby for game and I want to display player list. I'm watching for new players in room.
const [roomUsers, setRoomUsers] = useState([])
useEffect(() => {
if (room)
DB.collection("room_users").where("room_id", "==", room.id)
.onSnapshot((querySnapshot) => {
const newRoomUsers = [];
querySnapshot.forEach((doc) => {
newRoomUsers.push(doc.data());
});
setRoomUsers(newRoomUsers)
});
})
When I join in the second window, the number of players changes but the list updates over time. When I join more times, the list stops updating at all.
<h4>Players list ({roomUsers.length}/4)</h4>
{roomUsers.map(roomUser => (
<PlayerListItem
roomUser={roomUser}
owner={roomUser.uuid === room.owner}
/>
))}
In PlayerListItem component I am fetching the user via uuid
const [user, setUser] = useState(null)
useEffect(() => {
DB.collection('users').where('uuid', '==', roomUser.uuid).get().then(snapshot => {
if (!snapshot.empty)
setUser(snapshot.docs[0].data())
})
}, [])
Then I return the username through the component.
Sometimes the same username appears twice in the list instead of different
Your useEffect is missing the dependencies array. It should also return the cleanup function
useEffect(() => {
if (!room) {
return;
}
const unsub = DB.collection("room_users").where("room_id", "==", room.id)
.onSnapshot((querySnapshot) => {
const newRoomUsers = [];
querySnapshot.forEach((doc) => {
newRoomUsers.push(doc.data());
});
setRoomUsers((currVal) => {
console.log({currVal, newRoomUsers})
const newVal = [] // change this to be correct
return newVal
})
});
return () => unsub();
}, [room])

React-Native infinite loop

I am trying to get data from my firebase-firestore I an showing a loading state to wait for the data to load however when it does load it keeps returning the firestore data infinite times. Please may someone help me.
This is my code Paper is just a custom component
import Paper from '../Components/Paper'
import firebase from 'firebase'
import { useState } from 'react'
const Home = (props) => {
const renderMealItem = (itemData) =>{
return (
<Paper
title={itemData.item.name}
serves={itemData.item.servings}
time={itemData.item.time}
image={itemData.item.imageUri}
/>
)
}
const [loading, setLoading] = useState(false)
const [all, setAll] = useState([])
useEffect(() => {
setLoading(true)
checkReturn()
getUser()
},[])
const checkReturn = () => {
if(all !== undefined){
setLoading(false)
}
}
const getUser = async() => {
try {
await firebase.firestore()
.collection('Home')
.get()
.then(querySnapshot => {
querySnapshot.docs.forEach(doc => {
setAll(JSON.stringify(doc.data()));
});
});
}catch(err){
console.log(err)
}
}
return(
<View style={styles.flatContainer}>
<FlatList
data={all}
keyExtractor={(item, index) => index.toString()}
renderItem={renderMealItem}/>
</View>
)
}
useEffect without second parameter will get executes on each update.
useEffect(() => {
setLoading(true)
checkReturn()
getUser()
})
so this will set the loading and tries to get the user. and when the data comess from server, it will get runned again.
So you should change it to : useEffect(() => {...}, []) to only get executed on mount phase(start).
Update: you should check for return on every update, not only at start. so change the code to:
useEffect(() => {
setLoading(true)
getUser()
}, [])
useEffect(() => {
checkReturn()
})
Ps: there is another issue with your code as well:
querySnapshot.docs.forEach(doc => {
setAll(JSON.stringify(doc.data()));
});
maybe it should be like :
setAll(querySnapshot.docs.map(doc => JSON.stringify(doc.data())));
Try passing an empty array as an argument to useEffect like so:
useEffect(() => {
setLoading(true)
checkReturn()
getUser()
}, [])

React Native: Saving array via AsyncStorage and retrieving it

I am trying to get an array of objects from my Redux-Store state called user and save it to async storage and use useState with the response to set the state before I retrieve it and view it with the FlatList however I am getting an error along the lines of Warning: Can't perform a React state update on an unmounted component. The user details is being set to the redux store in another component and then being retrieved from the current component I am displaying. Please could I get your help . I would really appreciate it. Thank you in advance!!!
const TheUser = (props) => {
//user is an array from redux store
const user = useSelector(state => state.account.cookbook)
const [getUser, setGetUser] = useState()
const saveUserAsync = async () => {
await AsyncStorage.setItem('user', JSON.stringify(user))
}
saveUserAsync()
AsyncStorage.getItem('user').then(response => {
setGetUser(response)
})
return (
<FlatList
data={getUser}
keyExtractor={item => item.id}
renderItem={itemData =>
<MyUser
name={itemData.item.name}
image={itemData.item.imageUri}
details={itemData.item.details.val}
/>
}
/>
)
}
export default TheUser
You can use useEffect hook to solve this problem.
IS_MOUNTED variable will track if component is mounted or not.
let IS_MOUNTED = false; // global value
const TheUser = (props) => {
//user is an array from redux store
const user = useSelector(state => state.account.cookbook)
const [getUser, setGetUser] = useState()
const saveUserAsync = async () => {
await AsyncStorage.setItem('user', JSON.stringify(user))
}
AsyncStorage.getItem('user').then(response => {
if(IS_MOUNTED)
{
setGetUser(JSON.parse(response));
}
});
useEffect(() => {
IS_MOUNTED = true;
saveUserAsync();
return (() => {
IS_MOUNTED = false;
})
},[])
return (
<FlatList
data={getUser}
keyExtractor={item => item.id}
renderItem={itemData =>
<MyUser
name={itemData.item.name}
image={itemData.item.imageUri}
details={itemData.item.details.val}
/>
}
/>
)
}
export default TheUser
import { useEffect } from "react"
let isMount = true
const TheUser = (props) => {
//user is an array from redux store
const user = useSelector(state => state.account.cookbook)
// const [getUser, setGetUser] = useState()
// useEffect(() => {
// const saveUserAsync = async () => {
// await AsyncStorage.setItem('user', JSON.stringify(user))
// const response = await AsyncStorage.getItem('user')
// if (isMount)
// setGetUser(JSON.parse(response))
// }
// saveUserAsync()
// }, [user])
// useEffect(() => {
// isMount = true
// return () => {
// isMount = false
// }
// }, [])
return (
<FlatList
// data={getUser}
data={user}
keyExtractor={item => item.id}
renderItem={itemData =>
<MyUser
name={itemData.item.name}
image={itemData.item.imageUri}
details={itemData.item.details.val}
/>
}
/>
)
}
export default TheUser

React + Firebase function looping by accident

I have no idea why is my function looping when I use useStates,
Can anyone figure out the problem.
It loops over and over,this is what appears in my console.log inside the snapshot
`function Classtab() {
const [userName, setuserName] = React.useState(null)
const [userType, setuserType] = React.useState(null)
const [userEmail, setuserEmail] = React.useState(null)
const [userCourse, setuserCourse] = React.useState([])
const [registeredCourse, setregisteredCourse] = React.useState([])
firebase.auth().onAuthStateChanged((user) => {
if(user){
var db = firebase.firestore()
db.collection('user').doc(user.uid)
.get()
.then(snapshot => {
setuserName( snapshot.data().name)
setuserType( snapshot.data().type)
setuserCourse( snapshot.data().course)
setuserEmail( user.email)
console.log(userCourse)
userCourse.map(course => {
db.doc(course).get().then(
snapshot => {setregisteredCourse([...registeredCourse, snapshot.data().name])}
)
}
)
}).catch(error => console.log(error))}else{}
})
return(...)`
You need to move your auth code into useEffect. What's happening right now is that you are running onAuthStateChanged on every render. And each time that returns, it causes another render, causing it to infinitely add more subscriptions.
I've modified your code to prevent the infinite re-renders and allow userCourse to be the correct value in the promise.then function. What it was originally would've had userCourse in the function to always be an empty array (due to the closure).
function Classtab() {
const [userName, setuserName] = React.useState(null);
const [userType, setuserType] = React.useState(null);
const [userEmail, setuserEmail] = React.useState(null);
const [userCourse, setuserCourse] = React.useState([]);
const [registeredCourse, setregisteredCourse] = React.useState([]);
const registeredCourseRef = useRef(registeredCourse);
useEffect(()=>{
registeredCourseRef.current = registeredCourse;
},[registeredCourse])
useEffect(() => {
const unsubscribe = firebase.auth().onAuthStateChanged((user) => {
if (user) {
var db = firebase.firestore();
db.collection('user')
.doc(user.uid)
.get()
.then((snapshot) => {
setuserName(snapshot.data().name);
setuserType(snapshot.data().type);
const userCourse = snapshot.data().course;
setuserCourse(userCourse);
setuserEmail(user.email);
console.log(userCourse);
userCourse.map((course) => {
db.doc(course)
.get()
.then((snapshot) => {
setregisteredCourse((registeredCourse)=>[
...registeredCourse,
snapshot.data().name,
]);
});
});
})
.catch((error) => console.log(error));
} else {
}
});
return () => {
unsubscribe();
};
//Need to have registeredCourse in the dependency array
//Or have it in a ref
}, []);
// return(...)
}

Categories

Resources