You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.30 05:47:03