Reputation: 361
On Click I want to fetch onSnapshot instance of data which startAfter
current data. But When the request is sent for the 1st time it returns an empty array. I have to click twice to get new Data.
export default function Page() {
const [data, setData] = useState([])
const [newData, setNewData] = useState([])
const [loading, setLoading] = useState(false)
const [postsEnd, setPostsEnd] = useState(false)
const [postLimit, setPostLimit] = useState(25)
const [lastVisible, setLastVisible] = useState(null)
useEffect(() => {
const collectionRef = collection(firestore, "Page")
const queryResult = query(collectionRef, orderBy("CreatedOn", "desc"), limit(postLimit));
const unsubscribe = onSnapshot(queryResult, (querySnapshot) => {
setLastVisible(querySnapshot.docs[querySnapshot.docs.length-1])
setData(querySnapshot.docs.map(doc => ({
...doc.data(),
id: doc.id,
CreatedOn : doc.data().CreatedOn?.toDate(),
UpdatedOn : doc.data().UpdatedOn?.toDate()
}))
)
});
return unsubscribe;
}, [postLimit])
const ths = (
<tr>
<th>Title</th>
<th>Created</th>
<th>Updated</th>
</tr>
);
const fetchMore = async () => {
setLoading(true)
const collectionRef = collection(firestore, "Page")
const queryResult = query(collectionRef, orderBy("CreatedOn", "desc"), startAfter(lastVisible), limit(postLimit));
onSnapshot(await queryResult, (querySnapshot) => {
setLastVisible(querySnapshot.docs[querySnapshot.docs.length-1])
setNewData(querySnapshot.docs.map(doc => ({
...doc.data(),
id: doc.id,
CreatedOn : doc.data().CreatedOn?.toDate(),
UpdatedOn : doc.data().UpdatedOn?.toDate()
}))
)
});
//Commented If Statement cause data is not fetched completely
//if (newData < postLimit ) { setPostsEnd(true) }
setData(data.concat(newData))
setLoading(false)
console.log(newData)
}
return (
<>
<Table highlightOnHover>
<thead>{ths}</thead>
<tbody>{
data.length > 0
? data.map((element) => (
<tr key={element.id}>
<td>{element.id}</td>
<td>{element.CreatedOn.toString()}</td>
<td>{element.UpdatedOn.toString()}</td>
</tr>
))
: <tr><td>Loading... / No Data to Display</td></tr>
}</tbody>
</Table>
{!postsEnd && !loading ? <Button fullWidth variant="outline" onClick={fetchMore}>Load More</Button> : <></>}
</>
)
}
Current Result
Expected Result
Upvotes: 1
Views: 1661
Reputation: 2904
The problem seems to be rooted in React updating the state asynchronously. I saw the same behavior using your code in my Next.js project. When looking at it closely, the onSnapshot()
function and your query are working as expected.
You cannot await onSnapshot()
(code reference) since it's not an asynchronous function, so the async/await in fetchMore()
does not have any effect. Regardless, React state update is asynchronous, so setData(data.concat(newData))
might run before newData
is updated. By the second time you load more documents, newData
will have the document data.
It looks like fetchMore()
can be done without relying on a "newData" state, since it was only used to update the actual data and to hide the "Load More" button:
const fetchMore = () => {
setLoading(true)
const collectionRef = collection(firestore, "page")
const queryResult = query(collectionRef, orderBy("CreatedOn", "desc"), startAfter(lastVisible), limit(postLimit));
onSnapshot(queryResult, (querySnapshot) => {
setLastVisible(querySnapshot.docs[querySnapshot.docs.length - 1]);
//paginatedDocs simply holds the fetched documents
const paginatedDocs = querySnapshot.docs.map(doc => ({
...doc.data(),
id: doc.id,
CreatedOn: doc.data().CreatedOn?.toDate(),
UpdatedOn: doc.data().UpdatedOn?.toDate()
}));
//Replaced usage of newData here with the amount of documents in paginatedDocs
if (paginatedDocs.length < postLimit ) { setPostsEnd(true) }
//Updates the data used in the table directly, rebuilding the component
setData(data.concat(paginatedDocs));
});
setLoading(false);
}
With these changes, there is no problem updating the documents at the first try when clicking the button.
Upvotes: 3
Reputation: 1048
Do you have persistence on? It could be firing once with the local result and again with the server data.
Upvotes: 0