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

Next.js分类页面路径切换时产品列表不更新问题求助

Next.js 分类页面路由切换时产品列表不更新的修复方案

问题描述

开发基于Next.js的React项目,产品与多级分类数据通过API获取并展示在/categories/[slug].jsx页面,包含分类详情及对应产品列表:

  • 直接访问分类页面(如http://localhost/categories/category-one)时,数据加载正常,功能运行良好
  • 从页面侧边栏点击其他分类链接(如http://localhost/categories/category-two)时,URL路径和分类详情已更新,但产品列表仍显示旧分类内容,需手动刷新页面才能加载正确数据
  • 尝试使用useEffect监听路径变化但未解决问题

提供的代码

/categories/[slug].jsx

import { useRouter } from 'next/router';
import { useEffect, useState } from 'react';

import ProductArchive from '/ProductArchive';

import { getCategory, getCategoryByHierarchy, getCategorySlugs } from '/libs/categories';
import { getProductsByCatIds, getProductsCountByCatIds } from '/libs/products';

const CategoryScreen = ({ category, productsByCatIds, productsCount, categories }) => {
  const [products, setProducts] = useState(productsByCatIds);
  const [hasMore, setHasMore] = useState(true);

  const router = useRouter();

  const getMoreProducts = async () => {
    const moreProducts = await getProductsByCatIds({
      catId: category.term_id,
      perPage: 16,
      offset: products.length,
    });

    if (moreProducts.code === 'no_product') return;

    setProducts((products) => [...products, ...moreProducts]);
  };

  useEffect(() => {
    console.log('path got changed');
  }, [router.asPath]);

  useEffect(() => {
    setHasMore(productsCount > products.length ? true : false);
  }, [productsCount, products]);

  return (
    <>
      <ProductArchive
        products={products}
        getMoreProducts={getMoreProducts}
        hasMore={hasMore}
        productsCount={productsCount}
        banner={{ heading: category.name, i2Text: category.name }}
        data={categories}
      />
    </>
  );
};

export const getStaticPaths = async () => {
  const paths = await getCategorySlugs();

  return {
    paths,
    fallback: 'blocking',
  };
};

export const getStaticProps = async ({ params }) => {
  const category = await getCategory(params.slug);

  if (!category) {
    return {
      notFound: true,
    };
  }

  const productsByCatIds = await getProductsByCatIds({
    catId: category.id,
    perPage: 16,
  });

  const productsCount = await getProductsCountByCatIds({
    catId: category.id,
  });

  const categories = await getCategoryByHierarchy();

  const data = { category, productsByCatIds, productsCount, categories };

  return {
    props: data || {},
    revalidate: 1,
  };
};

export default CategoryScreen;

/ProductArchive.jsx

import Link from 'next/link';
import DefaultBanner from './DefaultBanner';
import ProductCard from './ProductCard';
import Sidebar from './Sidebar';

import InfiniteScroll from 'react-infinite-scroll-component';
import DefaultLoader from './DefaultLoader';
import { thousandSeparator } from '../../libs/functions/thousandSeparator';

const ProductArchive = ({ banner, products, getMoreProducts, hasMore, productsCount, data }) => {
  return (
    <section>
      <DefaultBanner heading={banner.heading} i2Text={banner.i2Text} />

      <div className="container">
        <div className="grid-view">
          <div className="col-1">
            <Sidebar data={data} />
          </div>

          <div className="col-2">
            <div className="relative">
              {productsCount > 0 && (
                <small className="total">
                  Total: {thousandSeparator(productsCount)}
                </small>
              )}
            </div>
            {productsCount > 0 ? (
              <InfiniteScroll
                dataLength={products.length}
                next={getMoreProducts}
                hasMore={hasMore}
                loader={<DefaultLoader />}
                endMessage={<p></p>}
                scrollThreshold="0.5"
              >
                <div className="another-grid">
                  {products.length > 0 ? (
                    products.map((product, index) => {
                      return (
                        <ProductCard
                          key={product.id}
                          index={index}
                          product={product}
                        />
                      );
                    })
                  ) : (
                    <div>
                      <h4 className="text-xl">Oops... No products found.</h4>
                      <p className="mt-2 font-thin">
                        <Link href="/">
                          <a>Go back to homepage</a>
                        </Link>
                      </p>
                    </div>
                  )}
                </div>
              </InfiniteScroll>
            ) : (
              <div>No products found.</div>
            )}
          </div>
        </div>
      </div>
    </section>
  );
};

export default ProductArchive;

问题根源

Next.js在客户端路由切换时,静态生成页面的getStaticProps不会重新执行,但组件会接收新的props。而你将products状态初始化为productsByCatIds后,没有在新props传入时更新该状态,导致产品列表始终停留在初始值。此外,getMoreProducts函数可能捕获旧的category实例,导致加载更多时仍使用旧分类ID。

修复方案

修改/categories/[slug].jsx组件,添加状态同步逻辑并优化函数依赖:

  1. 导入useCallback钩子
  2. 用useCallback包裹getMoreProducts,确保它始终使用最新的分类ID和产品长度
  3. 添加useEffect监听productsByCatIds变化,重置产品状态为新分类的初始产品
  4. 优化hasMore的重置逻辑,确保路由切换后正确判断是否还有更多产品

修改后的完整代码如下:

import { useRouter } from 'next/router';
import { useEffect, useState, useCallback } from 'react'; // 新增useCallback

import ProductArchive from '/ProductArchive';

import { getCategory, getCategoryByHierarchy, getCategorySlugs } from '/libs/categories';
import { getProductsByCatIds, getProductsCountByCatIds } from '/libs/products';

const CategoryScreen = ({ category, productsByCatIds, productsCount, categories }) => {
  const [products, setProducts] = useState(productsByCatIds);
  const [hasMore, setHasMore] = useState(true);

  const router = useRouter();

  // 用useCallback包裹,依赖最新的category.term_id和products.length
  const getMoreProducts = useCallback(async () => {
    const moreProducts = await getProductsByCatIds({
      catId: category.term_id,
      perPage: 16,
      offset: products.length,
    });

    if (moreProducts.code === 'no_product') {
      setHasMore(false);
      return;
    }

    setProducts((prevProducts) => [...prevProducts, ...moreProducts]);
  }, [category.term_id, products.length]);

  // 监听productsByCatIds变化,重置产品状态为新分类的初始产品
  useEffect(() => {
    setProducts(productsByCatIds);
  }, [productsByCatIds]);

  // 监听productsCount和products变化,更新hasMore状态
  useEffect(() => {
    setHasMore(productsCount > products.length);
  }, [productsCount, products]);

  return (
    <>
      <ProductArchive
        products={products}
        getMoreProducts={getMoreProducts}
        hasMore={hasMore}
        productsCount={productsCount}
        banner={{ heading: category.name, i2Text: category.name }}
        data={categories}
      />
    </>
  );
};

// 下面的getStaticPaths和getStaticProps保持不变
export const getStaticPaths = async () => {
  const paths = await getCategorySlugs();

  return {
    paths,
    fallback: 'blocking',
  };
};

export const getStaticProps = async ({ params }) => {
  const category = await getCategory(params.slug);

  if (!category) {
    return {
      notFound: true,
    };
  }

  const productsByCatIds = await getProductsByCatIds({
    catId: category.id,
    perPage: 16,
  });

  const productsCount = await getProductsCountByCatIds({
    catId: category.id,
  });

  const categories = await getCategoryByHierarchy();

  const data = { category, productsByCatIds, productsCount, categories };

  return {
    props: data || {},
    revalidate: 1,
  };
};

export default CategoryScreen;

修复说明

  • 状态同步:通过useEffect监听productsByCatIds的变化,当路由切换导致新的分类产品数据传入时,自动重置products状态为新分类的初始产品列表
  • 函数稳定性:用useCallback包裹getMoreProducts,确保它始终引用最新的category.term_id和products.length,避免加载更多时使用旧分类ID
  • 逻辑优化:简化hasMore的判断逻辑,确保路由切换后能正确判断是否还有更多产品可加载

内容的提问来源于stack exchange,提问作者Minn Khant Naing

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.18 00:15:43