Search code examples
javascriptreactjsuse-effect

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


Solution

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