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

React自定义Hook触发无限循环问题求助

React + Firebase任务管理器无限循环问题解决

问题背景

使用React和Firebase开发简易任务管理器,通过自定义useHttp Hook获取数据时,页面加载出现无限循环。需求是页面加载和提交新任务表单时各触发一次数据请求。

相关代码

触发Hook的组件代码

const AllTasks = () => {
  const [tasks, setTasks] = useState([]);

  const renderTasks = (taskObj) => {
    const loadedTasks = [];

    for (const key in taskObj) {
      loadedTasks.unshift({
        id: key,
        date: taskObj[key].Date,
        client: taskObj[key].Client,
        task: taskObj[key].Task,
        time: taskObj[key].Time,
      });
    }
    setTasks(loadedTasks);
  };

  const { sendRequest: retrieveTasks } = useHttp();

  // 原直接调用逻辑会触发无限循环,已注释
  // const refresh = retrieveTasks(
  //   {
  //     url: "https://myurl.com/alltasks.json",
  //   },
  //   renderTasks
  // );

  // 页面加载时触发请求(仅执行一次)
  useEffect(() => {
    retrieveTasks(
      {
        url: "https://myurl.com/alltasks.json",
      },
      renderTasks
    );
  }, []);

  // 表单相关逻辑
  const enteredDateRef = useRef();
  const enteredClientRef = useRef();
  const enteredTaskRef = useRef();
  const enteredTimeRef = useRef();

  const renderNewTask = (data) => {
    const taskData = {
      id: data.name,
      date: data.Date,
      client: data.Client,
      task: data.Task,
      time: data.Time,
    };

    setTasks((tasks) => [taskData, ...tasks]);
  };

  const { sendRequest: postTask } = useHttp();

  const submitTaskHandler = async (event) => {
    event.preventDefault();

    const enteredDate = enteredDateRef.current.value;
    const enteredClient = enteredClientRef.current.value;
    const enteredTask = enteredTaskRef.current.value;
    const enteredTime = enteredTimeRef.current.value;

    const newTaskData = {
      Date: enteredDate,
      Client: enteredClient,
      Task: enteredTask,
      Time: enteredTime,
    };

    postTask(
      {
        url: "https://myurl.com/alltasks.json",
        method: "POST",
        body: newTaskData,
        // 修正Content-Type的拼写错误
        headers: { "Content-Type": "application/json" },
      },
      renderNewTask
    );
  };

  // 修正refresh为函数,避免组件渲染时立即执行请求
  const refresh = () => {
    retrieveTasks(
      {
        url: "https://myurl.com/alltasks.json",
      },
      renderTasks
    );
  };

  return (
    <Fragment>
      <TasksHeader />

      <section className="w-full mt-4">
        <CardDark backgroundType="liquid">
          <h2>This month alone</h2>
          <div className="flex mt-4 justify-between">
            <div>
              <p className=" text-6xl font-medium">56.4</p>
              <p className="mt-2">Hours input</p>
            </div>
            <div className="border border-white"></div>
            <div>
              <p className=" text-6xl font-medium">1,378.45 €</p>
              <p className="mt-2">Cash generated</p>
            </div>
            <div className="border border-white"></div>
            <div>
              <p className=" text-6xl font-medium">128</p>
              <p className="mt-2">Tasks input</p>
            </div>
          </div>
        </CardDark>
      </section>

      <section>
        <SearchBar />
        <div className="flex justify-between mt-4">
          <h2>Your tasks</h2>
          <div className="flex">
            <h2 className="mr-2">Filters:</h2>
            <form className="text-gray-400" action="">
              <input type="date" className=" bg-gray-50 w-[120px] mr-2" />
              <input type="date" className=" bg-gray-50 w-[120px] mr-2" />
              <select id="cars" name="cars" className="bg-gray-50">
                <option value="volvo">Volvo</option>
                <option value="saab">Saab</option>
                <option value="fiat">Fiat</option>
                <option value="audi">Audi</option>
              </select>
            </form>
          </div>
        </div>
      </section>

      <section>
        <CardWhite>
          <form
            action=""
            onSubmit={submitTaskHandler}
            className="h-[50px] flex"
          >
            <input
              ref={enteredDateRef}
              type="date"
              className="h-[50px] focus:outline-none w-1/5 rounded-l-2xl border border-gray-300 px-4"
            />
            <input
              ref={enteredClientRef}
              type="text"
              className="h-[50px] focus:outline-none w-1/5 border-y border-gray-300 px-4"
              placeholder="Client name"
            />
            <input
              ref={enteredTaskRef}
              type="text"
              className="h-[50px] focus:outline-none grow border-y border-l border-gray-300 px-4"
              placeholder="Task"
            />
            <div className="h-[50px] flex w-1/10 rounded-r-2xl border border-gray-300 pl-4">
              <input
                ref={enteredTimeRef}
                type="time"
                className="focus:outline-none h-full"
              />
              <button type="submit" className="h-full p-2">
                <img src={SubmitIcon} className="h-full" alt="" />
              </button>
            </div>
          </form>
          <List
            tasks={tasks}
            refresh={refresh}
          />
        </CardWhite>
      </section>
    </Fragment>
  );
};

export default AllTasks;

自定义Hook代码

const useHttp = () => {
    
    const sendRequest = async (requestConfig, applyData) => {

    const response = await fetch(requestConfig.url, {
      method: requestConfig.method ? requestConfig.method : "GET",
      body: requestConfig.body ? JSON.stringify(requestConfig.body) : null,
      headers: requestConfig.headers ? requestConfig.headers : {},
    });

    const responseData = await response.json();

    applyData(responseData);

    console.log('request');
  };
  return { sendRequest };
};

export default useHttp;

问题原因及解决方案

无限循环原因

组件渲染时直接调用retrieveTasks,每次渲染都会执行该函数,函数内部调用setTasks更新状态,触发组件重新渲染,再次执行retrieveTasks,形成无限循环。

具体修复步骤

  1. 启用页面加载请求逻辑:取消注释useEffect代码块,将retrieveTasks调用放入其中,依赖项设为空数组[],确保仅在组件挂载时执行一次请求。
  2. 移除直接调用的请求:删除原直接调用retrieveTasks并赋值给refresh的代码,避免每次渲染都触发请求。
  3. 修正refresh定义:将refresh包装成函数,子组件调用时才触发请求,而非组件渲染时立即执行。
  4. 修复请求头错误:将postTask中的"application.json"改为"application/json",确保Firebase能正确解析请求体。

修改后,页面加载时触发一次数据请求,提交表单时触发一次新增任务请求,不会再出现无限循环问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.22 17:24:17