简体   繁体   English

为什么我的 useEffect 在 promise 被拒绝时无限运行,但在它被实现时只运行一次?

[英]Why my useEffect runs infinitely when promise gets rejected but only once when it gets fulfilled?

I am trying to fetch data from a mock backend created using JSON-server with the help of Axios.我正在尝试在 Axios 的帮助下从使用 JSON 服务器创建的模拟后端获取数据。 Now when the response status is 200 ie, data fetched successfully the state of my success variable changes to true.现在,当响应状态为 200 时,即成功获取数据时,我的成功变量的状态变为 true。 The useEffect runs only once and message toast appears only once. useEffect 只运行一次,消息 toast 只出现一次。 But now when there is an error while fetching the data the useEffect runs infinitely and toast starts appearing non-stop one after another.但是现在,当获取数据时出现错误时,useEffect 会无限运行,并且 toast 开始一个接一个地出现。 Can someone explain to me why is this happening and how am I able to solve this issue?有人可以向我解释为什么会发生这种情况,我该如何解决这个问题?

Below is the code I have written.下面是我写的代码。

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);
      })
      .catch((error) => {
        setLoading(false);
        setError({ status: true, message: error.message });
      });
  };

  useEffect(() => {
    getPosts();

    if (error.status) {
      toast.error(error.message);
    }

    if (success) toast.success('Success!');
  }, [success, error]);

  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 && 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;

  useEffect(() => {
    getPosts();

    if (error.status) {
      toast.error(error.message);
    }

    if (success) toast.success('Success!');
  }, [success, error]);

Since error is in the dependency array, any time the error changes, this effect will run.由于错误在依赖数组中,任何时候错误发生变化,这个效果都会运行。 So the error changes, which causes you to get the posts, which causes the error to change, etc.所以错误发生了变化,这会导致你得到帖子,这会导致错误发生变化,等等。

I would split this up into separate effects;我会将其拆分为单独的效果; one to kick off the load, and another to do the toasts:一个开始负载,另一个敬酒:

useEffect(() => {
  getPosts();
}, []);

useEffect(() => {
  if (error.status) {
    toast.error(error.message);
  }

  if (success) toast.success('Success!');
}, [sucess, error]);

Your useEffect has error as dependency and getPosts in its logic, so, if getPosts sets error (as it does) it creates an infinite loop.您的 useEffect 在其逻辑中具有依赖项和 getPosts 错误,因此,如果 getPosts 设置错误(因为它确实如此),它会创建一个无限循环。

useEffect => getPosts => setError => useEffect ... useEffect => getPosts => setError => useEffect ...

You can resolve this by deleting the error dependency of your useEffect, if you still want to refetch data, I think you should directly call getPosts in getPosts in a setTimeout for example.您可以通过删除 useEffect 的错误依赖项来解决此问题,如果您仍想重新获取数据,我认为您应该直接在 setTimeout 中的 getPosts 中调用 getPosts。

As others have mentioned before, your problem is with your useEffect triggering another getPosts call when it was only supposed to react to a change in success or error variables.正如其他人之前提到的,您的问题是您的useEffect触发另一个getPosts调用,而它只应该对成功或错误变量的变化做出反应。

When doing API calls inside an effect, please consider these: (#1 is based on personal opinion)在 effect 内部调用 API 时,请考虑以下几点:(#1 基于个人意见)

  1. Preferably handle the promise inside the effect (or make 2 separate effects: 1 to make the call and 1 to react to success / error)最好在效果内处理承诺(或制作 2 个单独的效果:1 个进行调用,1 个对成功/错误做出反应)
  2. Add a cleanup function to handle promise cancellation in case of a cleanup ( https://beta.reactjs.org/learn/synchronizing-with-effects#fetching-data )添加一个清理函数以在清理的情况下处理承诺取消( https://beta.reactjs.org/learn/synchronizing-with-effects#fetching-data

This is how I would probably structure it:这就是我可能会如何构建它:

useEffect(() => {
    const controller = new AbortController();
    axios
        .get('http://localhost:5050/posts', {
            signal: controller.signal
        })
        .then((res) => {
            setPosts(res.data);
            setLoading(false);
            if (res.status === 200) setSuccess(true);
        })
        .catch((error) => {
            setLoading(false);
            setError({ status: true, message: error.message });
        });

    return () => {
        controller.abort();
    }
}, []);

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM