Firebase分页故障:endBefore无法正确加载上一页数据
问题:Firestore分页上一页功能异常,总是返回第一组数据
在React应用中使用Firebase实现分页功能,期望基于当前数据组的最后/首个文档加载下一组/上一组数据。目前下一页分页功能正常,但上一页分页存在问题:每次尝试加载上一组数据时,总是返回第一组文档。已通过console.log确认传入endBefore的文档正确,尝试替换为endAt方法后问题依旧。
原代码
getDataUsers函数
import { FirebaseDB } from 'contexts/FirebaseContext'; import { collection, getDocs, limit, orderBy, query, startAfter, where, getCountFromServer, endBefore } from 'firebase/firestore'; const pageSize = 9; export const getDataUsers = async (user, startAfterDoc = null, endBeforeDoc = null) => { const data = []; const constraints = [collection(FirebaseDB, 'usuarios'), orderBy('efectividad', 'desc')]; const totalUsers = await getCountFromServer(...constraints); if (startAfterDoc) { constraints.push(startAfter(startAfterDoc)); } if (endBeforeDoc) { constraints.push(endBefore(endBeforeDoc)); } constraints.push(limit(pageSize)); const querySnapshot = await getDocs(query(...constraints)); querySnapshot.forEach((doc) => { const newData = { ...doc.data() }; delete newData.password; delete newData.URL_INE; delete newData.URL_REVERSO; data.push(newData); }); return { data, totalUsers: totalUsers.data().count, firstDoc: querySnapshot.docs[0], lastUser: querySnapshot.docs[querySnapshot.docs.length - 1] }; };
关联调用代码
const fetchDataUsers = async (startAfterDoc = null, endBeforeDoc = null) => { setIsLoading(true); try { const { data, totalUsers, firstDoc, lastUser } = await getDataUsers(user, startAfterDoc, endBeforeDoc); setDataUsers(data); lastU.current = lastUser; firstU.current = firstDoc; setIsLoading(false); return { totalUsers }; } catch (error) { console.log(error); setIsLoading(false); setOpenModalError(true); return []; } }; useEffect(() => { const firstRender = async () => { const { totalUsers } = await fetchDataUsers(); totalPages.current = Math.floor(totalUsers / 9); }; firstRender(); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const loadMore = async () => { await fetchDataUsers(lastU.current, null); setPage(page + 1); }; const loadPrevious = async () => { await fetchDataUsers(null, firstU.current); setPage(page - 1); };
问题原因
原上一页查询仅使用endBefore + limit的组合,Firestore的查询逻辑是:从排序的起始位置开始,获取到endBefore指定文档之前的最多pageSize条数据,因此每次都会返回从第一条开始的第一组数据,而非期望的上一页内容。
同时原代码中getCountFromServer的调用存在错误,该方法需要传入Query对象,而非直接传约束数组。
解决方案
调整上一页的查询逻辑,使用limitToLast(pageSize)结合endBefore,这样Firestore会从endBefore指定的文档位置往前取最多pageSize条数据,正好对应上一页的内容。同时修复getCountFromServer的调用错误。
修改后的代码
1. 更新getDataUsers函数
import { FirebaseDB } from 'contexts/FirebaseContext'; import { collection, getDocs, limit, orderBy, query, startAfter, getCountFromServer, endBefore, limitToLast } from 'firebase/firestore'; const pageSize = 9; export const getDataUsers = async (user, startAfterDoc = null, endBeforeDoc = null, isPrevious = false) => { const data = []; const constraints = [collection(FirebaseDB, 'usuarios'), orderBy('efectividad', 'desc')]; // 修复getCountFromServer的调用,传入Query对象 const totalUsers = await getCountFromServer(query(...constraints)); if (startAfterDoc) { // 下一页查询:从startAfterDoc之后取pageSize条数据 constraints.push(startAfter(startAfterDoc)); constraints.push(limit(pageSize)); } else if (endBeforeDoc && isPrevious) { // 上一页查询:取endBeforeDoc之前的最后pageSize条数据 constraints.push(endBefore(endBeforeDoc)); constraints.push(limitToLast(pageSize)); } else { // 首次加载 constraints.push(limit(pageSize)); } const querySnapshot = await getDocs(query(...constraints)); querySnapshot.forEach((doc) => { const newData = { ...doc.data() }; delete newData.password; delete newData.URL_INE; delete newData.URL_REVERSO; data.push(newData); }); return { data, totalUsers: totalUsers.data().count, firstDoc: querySnapshot.docs[0], lastUser: querySnapshot.docs[querySnapshot.docs.length - 1] }; };
2. 更新调用逻辑
// 更新fetchDataUsers,新增isPrevious参数 const fetchDataUsers = async (startAfterDoc = null, endBeforeDoc = null, isPrevious = false) => { setIsLoading(true); try { const { data, totalUsers, firstDoc, lastUser } = await getDataUsers(user, startAfterDoc, endBeforeDoc, isPrevious); setDataUsers(data); lastU.current = lastUser; firstU.current = firstDoc; setIsLoading(false); return { totalUsers }; } catch (error) { console.log(error); setIsLoading(false); setOpenModalError(true); return []; } }; // 更新loadPrevious函数,传入isPrevious标记 const loadPrevious = async () => { await fetchDataUsers(null, firstU.current, true); setPage(page - 1); };
说明
- 下一页逻辑保持不变,依然使用
startAfter + limit获取后续数据。 - 上一页逻辑改为
endBefore + limitToLast,确保从当前页的第一条数据之前,获取最多pageSize条数据,即上一页的内容。 - 修复了
getCountFromServer的调用错误,避免潜在的查询异常。
内容的提问来源于stack exchange,提问作者Fabian Cardenas Aldana
相关产品推荐
相关产品推荐

