延迟加载返回空数组

问题描述 投票:0回答:1
import React, { useState, useRef, useEffect } from "react";
import {
  collection,
  query,
  where,
  getDocs,
  startAfter,
  limit,
  startAt,
} from "firebase/firestore";
import { db } from "../firebase";
import * as XLSX from "xlsx";
import { useReactToPrint } from "react-to-print";
import { useSelector } from "react-redux";

const FilterProducts = () => {
  const [filteredProducts, setFilteredProducts] = useState([]);
  const processesList = useSelector((state) => state.processes.value);
  const [lastVisible, setLastVisible] = useState(null);
  const [hasMoreProducts, setHasMoreProducts] = useState(true);
  const [selectedProcess, setSelectedProcess] = useState("");
  const [startDate, setStartDate] = useState("");
  const [endDate, setEndDate] = useState("");
  const [errorMessage, setErrorMessage] = useState("");
  const [sortCriteria, setSortCriteria] = useState("");
  const [sortDirection, setSortDirection] = useState("asc");
  const tableRef = useRef();
  const loaderRef = useRef(null);

  useEffect(() => {
    if (filteredProducts.length > 0) {
      const sortedProducts = sortProducts(filteredProducts);
      setFilteredProducts([...sortedProducts]);
    }
  }, [sortCriteria, sortDirection]);

  const buildQueryWithFilters = (baseQuery) => {
    let queryDescription = "Query Filters: \n";

    if (startDate && endDate) {
      baseQuery = query(
        baseQuery,
        where("exFactoryDate", ">=", startDate),
        where("exFactoryDate", "<=", endDate)
      );
      queryDescription += `  - ExFactoryDate between ${startDate} and ${endDate}\n`;
    }
    if (selectedProcess) {
      baseQuery = query(
        baseQuery,
        where(`processes.${selectedProcess}`, "==", {})
      );
      queryDescription += `  - Process: ${selectedProcess}\n`;
    }

    console.log(queryDescription);

    return baseQuery;
  };

  const fetchFilteredProducts = async () => {
    if (!selectedProcess || !startDate || !endDate) {
      setErrorMessage(
        "Please select all filters: Process, Start Date, and End Date."
      );
      return;
    }
    try {
      let productsQuery = query(collection(db, "products"), limit(20));

      productsQuery = buildQueryWithFilters(productsQuery);
          console.log(
            "Products query after filters applied: ",
            JSON.stringify(productsQuery)
          );

      const productsSnapshot = await getDocs(productsQuery);
            console.log("Fetched products: ", productsSnapshot);

      const productsList = productsSnapshot.docs.map((doc) => ({
        id: doc.id,
        ...doc.data(),
      }));

      setFilteredProducts(productsList);
      setLastVisible(productsSnapshot.docs[productsSnapshot.docs.length - 1]);
      setHasMoreProducts(productsList.length === 20);
      setErrorMessage("");
    } catch (error) {
      setErrorMessage("Error fetching products. Please try again.");
    }
  };

  const fetchMoreProducts = async () => {
    if (!lastVisible || !hasMoreProducts) return;
    console.log("Last visible document ID: ", lastVisible.id);

    let productsQuery = query(
      collection(db, "products"),
      startAfter(lastVisible),
      limit(20)
    );

    productsQuery = buildQueryWithFilters(productsQuery);
    console.log("Products query after filters applied: ", JSON.stringify(productsQuery));

    try {
      const productsSnapshot = await getDocs(productsQuery);

      const productsList = productsSnapshot.docs.map((doc) => ({
        id: doc.id,
        ...doc.data(),
      }));

      console.log("Fetched products: ", productsSnapshot);

      setFilteredProducts((prevProducts) => [...prevProducts, ...productsList]);
      setLastVisible(productsSnapshot.docs[productsSnapshot.docs.length - 1]);
      setHasMoreProducts(productsList.length === 20);
    } catch (error) {
      console.error("Error fetching more products: ", error);
    }
  };

  useEffect(() => {
    const observer = new IntersectionObserver(
      (entries) => {
        if (entries[0].isIntersecting && hasMoreProducts) {
          console.log("Loader is visible, fetching more products...");
                console.log(lastVisible);
          fetchMoreProducts();
        }
      },
      { threshold: 1 }
    );
    if (loaderRef.current) {
      observer.observe(loaderRef.current);
    }
    return () => {
      if (loaderRef.current) {
        observer.unobserve(loaderRef.current);
      }
    };
  }, [loaderRef, lastVisible]);

  const sortProducts = (products) => {
    return products.sort((a, b) => {
      const aValue = a[sortCriteria];
      const bValue = b[sortCriteria];

      if (aValue < bValue) {
        return sortDirection === "asc" ? -1 : 1;
      }
      if (aValue > bValue) {
        return sortDirection === "asc" ? 1 : -1;
      }
      return 0;
    });
  };

  const handleProcessChange = (e) => {
    setSelectedProcess(e.target.value);
  };

  const handleStartDateChange = (e) => {
    setStartDate(e.target.value);
  };

  const handleEndDateChange = (e) => {
    setEndDate(e.target.value);
  };

  const handleSortChange = (criteria) => {
    if (sortCriteria === criteria) {
      setSortDirection(sortDirection === "asc" ? "desc" : "asc");
    } else {
      setSortCriteria(criteria);
      setSortDirection("asc");
    }
  };

  const handlePrint = useReactToPrint({ content: () => tableRef.current });

  const exportToExcel = () => {
    const headers = [
      "Sr. No",
      "Buyer",
      "Buyer PO",
      "Exfactory Date",
      "Shipment Date",
      "Style Name",
      "Size",
      "Color",
      "Quantity",
    ];

    const selectedProcessRow = [`${selectedProcess ? selectedProcess : ""}`];

    const data = filteredProducts.map((product) => [
      product.srNo,
      product.buyer,
      product.buyerPO,
      product.exFactoryDate,
      product.shipmentDate,
      product.styleName,
      product.size,
      product.color,
      product.quantity,
    ]);

    const worksheetData = [selectedProcessRow, headers, ...data];

    const worksheet = XLSX.utils.aoa_to_sheet(worksheetData);
    const workbook = XLSX.utils.book_new();
    XLSX.utils.book_append_sheet(workbook, worksheet, "Products");
    XLSX.writeFile(
      workbook,
      selectedProcess ? selectedProcess + ".xlsx" : "process.xlsx"
    );
  };

  const handleFilterClick = () => {
    if (!selectedProcess || !startDate || !endDate) {
      setErrorMessage(
        "Please select all filters: Process, Start Date, and End Date."
      );
    } else {
      setErrorMessage("");
      fetchFilteredProducts(); // Pass true to reset the pagination
    }
  };

  return (
    <div className="container mx-auto p-4">
      <h1 className="text-2xl font-bold mb-4">Planning Sheet</h1>
      <div className="mb-4">
        <label className="block text-gray-700">Process:</label>
        <select
          value={selectedProcess}
          onChange={handleProcessChange}
          className="p-2 border rounded w-full"
        >
          <option value="">All Processes</option>
          {processesList.map((process, index) => (
            <option key={index} value={process}>
              {process}
            </option>
          ))}
        </select>
      </div>
      <div className="mb-4">
        <label className="block text-gray-700">Start Date:</label>
        <input
          type="date"
          value={startDate}
          onChange={handleStartDateChange}
          className="p-2 border rounded w-full"
        />
      </div>
      <div className="mb-4">
        <label className="block text-gray-700">End Date:</label>
        <input
          type="date"
          value={endDate}
          onChange={handleEndDateChange}
          className="p-2 border rounded w-full"
        />
      </div>
      <button
        onClick={handleFilterClick}
        className="bg-blue-500 text-white px-4 py-2 rounded mb-4 hover:bg-blue-600"
      >
        Filter
      </button>
      {errorMessage && <div className="text-red-500 mb-4">{errorMessage}</div>}

      {filteredProducts.length > 0 && (
        <>
          <button
            onClick={handlePrint}
            className="bg-red-500 text-white px-4 py-2 rounded mb-4 hover:bg-red-600"
          >
            Print
          </button>
          <button
            onClick={exportToExcel}
            className="bg-green-500 text-white px-4 py-2 rounded mb-4 hover:bg-green-600"
          >
            Export to Excel
          </button>
          <div className="overflow-x-auto">
            <table
              ref={tableRef}
              className="min-w-full bg-white border border-gray-300"
            >
              <thead>
                <tr>
                  {[
                    "srNo",
                    "buyer",
                    "buyerPO",
                    "exFactoryDate",
                    "shipmentDate",
                    "styleName",
                    "size",
                    "color",
                    "quantity",
                  ].map((field, index) => (
                    <th
                      key={index}
                      className="py-2 px-4 border-b border-gray-300 cursor-pointer"
                      onClick={() => handleSortChange(field)}
                    >
                      {field.charAt(0).toUpperCase() + field.slice(1)}
                      {sortCriteria === field && (
                        <span>{sortDirection === "asc" ? " ▲" : " ▼"}</span>
                      )}
                    </th>
                  ))}
                </tr>
              </thead>
              <tbody>
                {filteredProducts.map((product) => (
                  <tr
                    key={product.id}
                    className="cursor-pointer hover:bg-gray-100"
                  >
                    <td className="py-2 px-4 border-b border-gray-300 text-center">
                      {product.srNo}
                    </td>
                    <td className="py-2 px-4 border-b border-gray-300 text-center">
                      {product.buyer}
                    </td>
                    <td className="text-center py-2 px-4 border-b border-gray-300">
                      {product.buyerPO}
                    </td>
                    <td className="text-center py-2 px-4 border-b border-gray-300">
                      {product.exFactoryDate}
                    </td>
                    <td className=" text-center py-2 px-4 border-b border-gray-300">
                      {product.shipmentDate}
                    </td>
                    <td className="py-2 px-4 text-center border-b border-gray-300">
                      {product.styleName}
                    </td>
                    <td className="py-2 px-4 border-b text-center border-gray-300">
                      {product.size}
                    </td>
                    <td className="py-2 px-4 border-b border-gray-300 text-center">
                      {product.color}
                    </td>
                    <td className="py-2 px-4 border-b border-gray-300 text-center">
                      {product.quantity}
                    </td>
                  </tr>
                ))}
              </tbody>
            </table>
          </div>
          {hasMoreProducts ? (
            <div ref={loaderRef} className="py-4 text-center">
              <span>Loading more products...</span>
            </div>
          ) : (
            <div className="py-4 text-center">
              <span>No more products to load</span>
            </div>
          )}
        </>
      )}
    </div>
  );
};

export default FilterProducts;

实际上,在这个文件中的 fetchMoreProducts 函数中

 const fetchMoreProducts = async () => {
    if (!lastVisible || !hasMoreProducts) return;
    console.log("Last visible document ID: ", lastVisible.id);

    let productsQuery = query(
      collection(db, "products"),
      startAfter(lastVisible),
      limit(20)
    );

    productsQuery = buildQueryWithFilters(productsQuery);
    console.log("Products query after filters applied: ", JSON.stringify(productsQuery));

    try {
      const productsSnapshot = await getDocs(productsQuery);

      const productsList = productsSnapshot.docs.map((doc) => ({
        id: doc.id,
        ...doc.data(),
      }));

      console.log("Fetched products: ", productsSnapshot);

      setFilteredProducts((prevProducts) => [...prevProducts, ...productsList]);
      setLastVisible(productsSnapshot.docs[productsSnapshot.docs.length - 1]);
      setHasMoreProducts(productsList.length === 20);
    } catch (error) {
      console.error("Error fetching more products: ", error);
    }
  };

它无法从数据库中获取任何内容

现在,我确实尝试从查询中删除 startAfter 并获得输出,但它重新获取了之前的 20 个产品。我希望它只能在先前获取的最后一个产品之后获取产品。我愿意接受提问...

reactjs google-cloud-firestore lazy-loading redux-firestore
1个回答
0
投票

您共享的代码似乎都没有给出

lastVisible
值,因此您将
null
传递给
startAfter
- 这解释了为什么您没有得到结果。

如果

null
有值,则仅向查询传递
startAfter
子句,而不是传递
lastVisible

© www.soinside.com 2019 - 2024. All rights reserved.