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

使用useNavigate跳转时useEffect未触发、useState初始值异常问题

React SpinnerLoading组件路由跳转不显示问题排查与解决

组件代码与使用方式

SpinnerLoading组件代码

import { useEffect, useState } from "react";
import PropagateLoader from "react-spinners/PropagateLoader";
import { motion } from "framer-motion";

const SpinnerLoading = ({ children }) => {
  const [loading, setLoading] = useState(true);

  console.log(loading); // 第一处console.log

  useEffect(() => {
    const timeout = setTimeout(() => setLoading(false), 1000);
    console.log(loading); // 第二处console.log
    return () => clearTimeout(timeout);
  }, []);

  return (
    <>
      {loading ? (
        <PropagateLoader
          color="#36d7b7"
          cssOverride={{
            display: "block",
            position: "fixed",
            top: "50%",
            left: "50%",
            transform: "translate(-50%, -50%)",
          }}
        />
      ) : (
        <motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }}>
          {children}
        </motion.div>
      )}
    </>
  );
};

export default SpinnerLoading;

路由使用方式

<Route path="/" element={<SpinnerLoading><Homepage /></SpinnerLoading>} />

问题描述

  • 手动刷新页面时,Spinner正常显示;但通过useNavigate编程式跳转至该页面时,子组件直接加载,Spinner完全不显示。
  • 添加的两处console.log表现异常:手动刷新时两处都会触发;编程式跳转时仅第一处触发,且输出值为false,第二处(useEffect内)完全没执行。

原因解析

  1. React Router的组件复用机制:使用useNavigate跳转时,React Router会复用已经挂载在DOM中的组件实例(包括SpinnerLoading),不会重新创建新的组件实例。而空依赖数组的useEffect只会在组件首次挂载时执行一次,复用阶段不会再次触发。
  2. 状态留存问题:组件复用时,内部的state会保留上一次的状态值。第一次进入页面后loading会被设为false,当再次跳转回来时,loading的初始值不会重新设为true,而是直接沿用之前的false状态,所以直接渲染子组件,Spinner不显示。

解决方案

通过监听路由变化,在每次进入该页面时重置loading状态,确保Spinner能正常触发。修改后的组件代码如下:

import { useEffect, useState } from "react";
import PropagateLoader from "react-spinners/PropagateLoader";
import { motion } from "framer-motion";
import { useLocation } from "react-router-dom";

const SpinnerLoading = ({ children }) => {
  const [loading, setLoading] = useState(true);
  const location = useLocation();

  useEffect(() => {
    // 每次路由进入时重置loading为true
    setLoading(true);
    const timeout = setTimeout(() => setLoading(false), 1000);
    // 组件卸载或路由变化时清除定时器,避免内存泄漏
    return () => clearTimeout(timeout);
  }, [location.pathname]); // 依赖路由pathname,路由变化时重新执行effect

  return (
    <>
      {loading ? (
        <PropagateLoader
          color="#36d7b7"
          cssOverride={{
            display: "block",
            position: "fixed",
            top: "50%",
            left: "50%",
            transform: "translate(-50%, -50%)",
          }}
        />
      ) : (
        <motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }}>
          {children}
        </motion.div>
      )}
    </>
  );
};

export default SpinnerLoading;

修改说明

  • 引入useLocation钩子获取当前路由的路径信息;
  • 将useEffect的依赖改为location.pathname,这样每次路由切换到该页面时,都会重新执行effect;
  • 在effect开头先将loading设为true,确保进入页面时优先显示Spinner,再在定时器结束后切换为子组件。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 06:55:37