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

React useEffect首次加载页面时返回空数据数组的问题求助

问题分析与解决方案

你的核心问题在于数据获取和状态更新的时机错位,以及不必要的状态重复维护,导致首次加载时分类数据为空,具体问题点:

  1. 原useEffect中先执行了过滤逻辑(此时products是空数组),再调用fetchProducts,提前把isLoading设为false,页面直接渲染空内容;
  2. fetchProducts里用map逐个调用setProducts,多次触发状态更新且效率低下;
  3. 未清理Firebase的实时监听,存在内存泄漏风险;
  4. 单独维护多个分类状态,导致状态同步逻辑复杂。

下面给出两种可行的解决方案:


方案一:在数据回调内完成所有状态更新

直接在Firebase数据返回的回调里处理过滤和状态设置,确保数据就绪后再更新UI状态:

import React, { useState, useEffect } from "react";
import { Link } from "react-router-dom";
import { motion } from "framer-motion";
import { getDatabase, ref, onValue, off } from "firebase/database";
import Helmet from "../components/Helmet/Helmet";
import "../styles/home.css";

import { Container, Row, Col } from "reactstrap";
import heroImg from "../assets/images/hero-img.png";

import Services from "../services/Services";
import ProductsList from "../components/UI/ProductsList";
import Clock from "../components/UI/Clock";

import counterImg from "../assets/images/counter-timer-img.png";

const Home = () => {
  const [isLoading, setIsLoading] = useState(true);
  const [products, setProducts] = useState([]);
  const [trendingProducts, setTrendingProducts] = useState([]);
  const [bestSalesProducts, setBestSalesProducts] = useState([]);
  const [mobileProducts, setMobileProducts] = useState([]);
  const [wirelessProducts, setWirelessProducts] = useState([]);
  const [popularProducts, setPopularProducts] = useState([]);

  const year = new Date().getFullYear();

  useEffect(() => {
    const db = getDatabase();
    const thumbnailRef = ref(db, "Contents/");

    // 定义数据处理回调
    const handleDataSnapshot = (snapshot) => {
      const data = snapshot.val();
      if (data !== null) {
        // 一次性获取所有产品数组,避免多次状态更新
        const allProducts = Object.values(data);
        setProducts(allProducts);

        // 直接在数据就绪后过滤分类
        setTrendingProducts(allProducts.filter(item => item.category === "Kids"));
        setBestSalesProducts(allProducts.filter(item => item.category === "Entertainment"));
        setMobileProducts(allProducts.filter(item => item.category === "LifeStyle"));
        setWirelessProducts(allProducts.filter(item => item.category === "wireless"));
        setPopularProducts(allProducts.filter(item => item.category === "watch"));
      } else {
        // 数据为空时重置所有状态
        setProducts([]);
        setTrendingProducts([]);
        setBestSalesProducts([]);
        setMobileProducts([]);
        setWirelessProducts([]);
        setPopularProducts([]);
      }
      // 数据处理完成后关闭加载状态
      setIsLoading(false);
    };

    // 启动Firebase实时监听
    onValue(thumbnailRef, handleDataSnapshot);

    // 组件卸载时清理监听,防止内存泄漏
    return () => {
      off(thumbnailRef, "value", handleDataSnapshot);
    };
  }, []); // 依赖为空数组,仅首次加载执行

  if (isLoading) {
    return <div> Loading ... </div>;
  }

  return (
    <Helmet>
      <section className="hero__section">
        <Container>
          <Row>
            <Col lg="6" md="12" className="hero__content">
              <h2>Welcome to {year} Trend</h2>
              <motion.button
                whileTap={{ scale: 1.2 }}
                className="buy__btn store__btn"
              >
                <Link to="/shop">Visit Store</Link>
              </motion.button>
            </Col>
            <Col lg="6" md="12" className="text-end counter__img">
              <img src={counterImg} alt="counter" />
            </Col>
          </Row>
        </Container>
      </section>

      <section className="new__arrivals">
        <Container>
          <Row>
            <Col lg="12" className="text-center mb-5">
              <h2 className="section__title">New Arrivals</h2>
            </Col>
            <ProductsList data={mobileProducts} />
            <ProductsList data={wirelessProducts} />
          </Row>
        </Container>
      </section>
      <section className="popular_category">
        <Container>
          <Row>
            <Col lg="12" className="text-center mb-5">
              <h2 className="section__title">Popular in Category</h2>
            </Col>
            <ProductsList data={popularProducts} />
          </Row>
        </Container>
      </section>
    </Helmet>
  );
};

export default Home;

方案二:用useMemo派生分类状态(更推荐)

去掉多余的分类状态,通过useMemo从products派生分类数据,减少状态维护成本:

import React, { useState, useEffect, useMemo } from "react";
// 其他导入内容与方案一一致

const Home = () => {
  const [isLoading, setIsLoading] = useState(true);
  const [products, setProducts] = useState([]);

  const year = new Date().getFullYear();

  useEffect(() => {
    const db = getDatabase();
    const thumbnailRef = ref(db, "Contents/");

    const handleDataSnapshot = (snapshot) => {
      const data = snapshot.val();
      setProducts(data ? Object.values(data) : []);
      setIsLoading(false);
    };

    onValue(thumbnailRef, handleDataSnapshot);

    return () => {
      off(thumbnailRef, "value", handleDataSnapshot);
    };
  }, []);

  // 用useMemo派生分类数据,仅当products变化时重新计算
  const trendingProducts = useMemo(() => products.filter(item => item.category === "Kids"), [products]);
  const bestSalesProducts = useMemo(() => products.filter(item => item.category === "Entertainment"), [products]);
  const mobileProducts = useMemo(() => products.filter(item => item.category === "LifeStyle"), [products]);
  const wirelessProducts = useMemo(() => products.filter(item => item.category === "wireless"), [products]);
  const popularProducts = useMemo(() => products.filter(item => item.category === "watch"), [products]);

  if (isLoading) {
    return <div> Loading ... </div>;
  }

  // 返回的JSX内容与方案一一致
};

export default Home;

关键修改点说明

  1. 调整执行顺序:确保数据获取完成后再处理过滤和状态更新,避免提前渲染空内容;
  2. 一次性更新产品状态:用Object.values(data)直接获取完整产品数组,减少状态更新次数;
  3. 清理监听:组件卸载时取消Firebase实时监听,避免内存泄漏;
  4. 派生状态优化:方案二中用useMemo减少不必要的状态变量,逻辑更简洁且性能更优。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.11 09:15:59