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

React useEffect请求reject时无限循环、成功时仅执行一次的修复

问题原因
  • useEffect 对依赖项的变更做 浅比较 校验:原始类型值变了、或者引用类型的内存地址变了,就会重新执行回调。你写的effect依赖数组是[success, error],两个状态任意一个变动都会触发回调重跑。
  • 请求成功时不会死循环:组件首次挂载执行effect调用接口,返回200后执行setSuccess(true),success从false变true触发一次effect重跑;重跑后再次调用接口依然返回成功,此时success已经是true,重复设置相同布尔值不会触发状态更新,且全程不会进入catch修改error,error始终是初始化时的对象引用,依赖不再变化,循环终止,所以成功提示只弹一次。
  • 请求报错时死循环的触发链路:首次执行effect调用接口失败 → 进入catch执行setError({ status: true, message: error.message }) → 每次setError传入的都是 新建的对象字面量,哪怕内容和之前完全一样,内存地址和旧状态不同,React判定error依赖变动 → 触发effect重跑 → 再次调用getPosts()发起请求 → 请求再次失败 → 再次setError传入新对象 → 再次触发effect重跑,无限往复,导致错误提示不停弹出。
  • 根因是逻辑耦合:把「组件挂载时拉取一次数据」的一次性逻辑,和「监听状态弹提示」的逻辑混在了同一个effect里,再加上对象状态更新时每次生成新引用,共同触发了死循环。
修复方案
  • 拆分耦合逻辑:接口拉取逻辑单独放在挂载阶段执行的effect中,依赖数组传[],保证整个生命周期只调用一次,不会被状态变更触发重复请求。
  • toast提示直接放在请求的then/catch回调里触发,不需要额外监听状态,从根源避免重复执行。
  • 修正后的完整代码如下:
import React, { useState, useEffect } from 'react';
import Loader from './../components/Loader';
import axios from 'axios';
import { toast } from 'react-toastify';

const PostsTD = () => {
  const [posts, setPosts] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState({
    status: false,
    message: '',
  });
  const [success, setSuccess] = useState(false);

  const getPosts = () => {
    axios
      .get('http://localhost:5050/posts')
      .then((res) => {
        setPosts(res.data);
        setLoading(false);
        if (res.status === 200) {
          setSuccess(true);
          toast.success('Success!');
        }
      })
      .catch((error) => {
        setLoading(false);
        setError({ status: true, message: error.message });
        toast.error(error.message);
      });
  };

  // 仅组件挂载时执行一次数据拉取
  useEffect(() => {
    getPosts();
  }, []);

  if (loading) {
    return (
      <div className="px-28 text-center py-12">
        <Loader />
      </div>
    );
  }

  return (
    <div className="md:px-28 px-6">
      <h1 className="text-center font-extrabold text-gray-400 my-4 text-4xl">POSTS FETCHED USING AXIOS</h1>

      {posts?.length > 0 ? (
        <div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-3 py-2 px-3 bg-red-500">
          {posts.map((post, idx) => {
            return (
              <div key={idx} className="p-3 bg-red-200 text-gray-900 rounded-md">
                <h2 className="font-semibold text-xl">{post.title}</h2>
                <p className="font-normal my-3">{post.body}</p>
              </div>
            );
          })}
        </div>
      ) : (
        <h1>NO posts to render</h1>
      )}
    </div>
  );
};

export default PostsTD;
  • 可选优化:如果业务场景确实需要监听状态触发副作用,单独拆分一个effect处理提示逻辑,不要在这个effect中包含请求调用;更新对象/数组类型的状态时,先判断内容是否真的发生变化再调用setState,避免无意义的新引用触发重渲染或effect执行。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 04:06:06