Home > database >  Why my useEffect runs infinitely when promise gets rejected but only once when it gets fulfilled?
Why my useEffect runs infinitely when promise gets rejected but only once when it gets fulfilled?

Time:07-03

I am trying to fetch data from a mock backend created using JSON-server with the help of Axios. Now when the response status is 200 i.e., data fetched successfully the state of my success variable changes to true. The useEffect runs only once and message toast appears only once. But now when there is an error while fetching the data the useEffect runs infinitely and toast starts appearing non-stop one after another. 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;

CodePudding user response:

  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]);

CodePudding user response:

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 => 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.

CodePudding user response:

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.

When doing API calls inside an effect, please consider these: (#1 is based on personal opinion)

  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. Add a cleanup function to handle promise cancellation in case of a cleanup (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();
    }
}, []);
  • Related