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

React Firebase待办应用:按created字段排序任务失败求助

问题:React + Firebase 待办应用排序与过滤失效

我开发的React+Firebase待办清单应用无法实现过滤任务后按created字段排序的功能,尝试在数据库查询中添加orderBy("created")但未生效,甚至未过滤时新增任务的顺序也不对。

尝试过的查询语句:

const q = query(collection(db, 'tasks'), where("uid", "==", user?.uid), orderBy("created"));

完整代码(不含导入):

export default function Todo() {

    const [name, setName] = useState("");
    const [tasks, setTasks] = useState([]);
    const [user, loading, error] = useAuthState(auth);
    const [searchInput, setSearchInput] = useState("");
    const [filteredTasks, setFilteredTasks] = useState([]);

    // 获取任务数据
    useEffect(() => {
        const q = query(collection(db, 'tasks'), where("uid", "==", user?.uid));
        onSnapshot(q, (querySnapshot) => {
            setTasks(querySnapshot.docs.map(doc => ({
                id: doc.id,
                data: doc.data()
            })))
        })
    }, [])

    // 过滤任务
    useEffect(() => {
        setFilteredTasks(tasks
            .filter(task => task.data.name.match(searchInput))
        );
    }, [searchInput, tasks])

    // 主输入框值监听
    function handleChange(e) {
        e.preventDefault();
        setName(e.target.value);
        console.log(name);
    }

    // 搜索输入框值监听
    function handleSearchChange(e) {
        e.preventDefault();
        setSearchInput(e.target.value)
    }

    // 添加任务到Firestore
    const handleAdd = async (e) => {
        e.preventDefault();
        if (name === "") {
            alert("Please enter some text");
            clearInput();
            return;
        }
        try {
            await addDoc(collection(db, 'tasks'), {
                name: name,
                completed: false,
                created: Timestamp.now(),
                uid: user?.uid,
            })
            clearInput();
        } catch (err) {
            alert(err)
        }
    }

    // 双击标记任务完成
    function handleClick(e) {
        if (e.detail === 2) {
            console.log("double click");
            e.currentTarget.classList.toggle('double-clicked');
        }
    }

    // 更新任务名称
    const handleUpdate = async (taskName, id) => {
        let name = prompt("Please enter a new name", taskName);
        if (name === null) {
            return;
        }
        const taskDocRef = doc(db, 'tasks', id)
        try {
            await updateDoc(taskDocRef, {
                name: name,
            })
            clearInput();
        } catch (err) {
            alert(err)
        }
    }

    // 删除任务
    const handleDelete = async (id) => {
        console.log(id);
        const taskDocRef = doc(db, 'tasks', id)
        try {
            await deleteDoc(taskDocRef)
            clearInput();
        } catch (err) {
            alert(err)
        }
    }

    // 清空并聚焦输入框
    function clearInput() {
        let input = document.querySelector("input");
        input.value = '';
        input.focus();
        setName("");
    }

    return (
        <div>
            <div>
                <div>
                    <h1>To Do List App</h1>
                    <p>Double click to mark an item off.</p>
                </div>
                <input
                    id="input"
                    type="text"
                    value={name}
                    onChange={handleChange}
                    autoFocus
                />
                <button
                    className="add-button"
                    type="submit"
                    onClick={handleAdd}
                >
                    <IoMdAddCircle />
                </button>
            </div>

            <ol>
                {filteredTasks.map(task => (
                    <li
                        className="task-list-items"
                        id={task.id}
                        key={task.id}
                        completed={task.data.completed}
                        onClick={handleClick}
                    >
                        {task.data.name}
                        <button
                            className="edit-button"
                            onClick={() => handleUpdate(task.data.name, task.id)}
                        >
                            <BsFillPencilFill />
                        </button>
                        <button
                            className="delete-button"
                            onClick={() => handleDelete(task.id)}
                        >
                            <BsFillTrashFill />
                        </button>
                    </li>
                ))}
            </ol>

            <div>
                <h5>Search for an item by name</h5>
                <input
                    id="search-bar"
                    type="text"
                    value={searchInput}
                    onChange={handleSearchChange}
                />
            </div>

        </div>
    );
};

数据库结构:每个任务包含name、completed、created(时间戳类型)、uid字段。


解决方案

1. 修复初始数据查询的排序逻辑

你的初始查询未添加orderBy("created"),且useEffect依赖数组为空,导致user加载完成后不会重新触发查询(初始user?.uid可能为undefined)。修改后的代码:

useEffect(() => {
    // 确保user存在再发起查询
    if (!user) return;
    const q = query(collection(db, 'tasks'), where("uid", "==", user.uid), orderBy("created"));
    const unsubscribe = onSnapshot(q, (querySnapshot) => {
        setTasks(querySnapshot.docs.map(doc => ({
            id: doc.id,
            data: doc.data()
        })))
    })
    // 组件卸载时取消订阅
    return unsubscribe;
}, [user])

2. 过滤后维持排序规则

当前过滤逻辑仅做了筛选,未显式维持排序(查询已排序但过滤后需确保顺序),同时优化搜索匹配逻辑:

useEffect(() => {
    const result = tasks
        .filter(task => {
            // 空搜索时返回所有任务,同时实现大小写不敏感匹配
            if (!searchInput) return true;
            return task.data.name.toLowerCase().includes(searchInput.toLowerCase());
        })
        // 按created时间戳升序排序(最新任务在最后;需最新在前则添加.reverse())
        .sort((a, b) => a.data.created.seconds - b.data.created.seconds);
    setFilteredTasks(result);
}, [searchInput, tasks])

3. 创建Firestore复合索引

同时使用where和orderBy时,Firestore需要对应的复合索引。如果查询报错,控制台会提供直接创建索引的链接,点击后按提示完成创建即可。


内容的提问来源于stack exchange,提问作者Julian

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 17:30:21