2

i have this function in useEffect which fetch posts from database and then set posts state then sending them to Posts component, i put the posts state in the useEffect dependency, so if a new post is created by user, the Posts component re-render so the user doesn't need to refresh the page and it worked fine !! but i noticed at the network tab, requests to http://localhost:8000/home/${user._id} are being sent infinitely, i don't know why is that happening

const [posts, setPosts] = useState([]);
  const { user, isLoading, error } = useContext(AuthContext);

  useEffect(() => {
    const getPosts = async () => {
      const response = id
        ? await fetch(`http://localhost:8000/${id}`)
        : await fetch(`http://localhost:8000/home/${user._id}`);

      const data = await response.json();
      setPosts(
        data.sort((p1, p2) => {
          return new Date(p2.createdAt) - new Date(p1.createdAt);
        })
      );
    };

    getPosts();
  }, [user, id, user?._id, posts]);

  return (
    <div>
      {posts.map((post) => (
        <Posts key={post._id} post={post} />
      ))}
    </div>
  );
Emile Bergeron
  • 17,074
  • 5
  • 83
  • 129
aasem shoshari
  • 190
  • 2
  • 14
  • You've added `posts` to the dependency of an effect that sets `posts`, so it triggers itself again. – Emile Bergeron Apr 08 '22 at 19:41
  • Does this answer your question? [Infinite loop in useEffect](https://stackoverflow.com/questions/53070970/infinite-loop-in-useeffect) – pilchard Apr 08 '22 at 19:47

3 Answers3

1

In the dependencies of your useEffect call, you've included posts. This is both unnecessary (posts isn't used inside the effect) and the cause of your troubles. Per the docs, everytime one of your dependencies changes, the effect is fired. Since getPosts modifies posts by calling setPosts, you have an infinite loop. The solution is to remove posts from the effect dependencies.

From your comments, it seems you also have a second question, which is, "how can I trigger a fetch operation when an external service (http://localhost:8000/) has an update ready for me?" There are a variety of answers to this question, but this simplest is to poll the external service on a timer. Something to the effect of the following should do:

useEffect(() => {
  ...
  // fire getPosts once per second
  const timer = setInterval(getPosts, 1000);
  // clean up the timer whenever the effect is re-fired or the 
  // component unmounts
  return () => clearInterval(timer);
}, [dependencies...]);

The cleanup step is critical. Without it, you will end up with multiple timers running at different intervals, and the timer(s) will not stop if the component is unmounted. Please see effects with cleanup in the docs for details.

thisisrandy
  • 2,660
  • 2
  • 12
  • 25
  • yeah but that wont make the ```Posts``` component re-render on posts state change, which is what i want – aasem shoshari Apr 08 '22 at 19:53
  • When a component's [props](https://reactjs.org/docs/components-and-props.html), in this case, `key` and `post` change, it will be automatically rerendered. Your effect *fetches* data and *sets* it to a state variable (`posts`) using `setPosts`. When `posts` changes, react sees this and *reacts* to the state change by rerendering dependent components, in this case both `Posts` and the enclosing component in your question. – thisisrandy Apr 08 '22 at 20:10
  • You don't need to do anything other than use the state `posts`. React takes care of all the rest automatically. – thisisrandy Apr 08 '22 at 20:12
  • no, if i create new post in ```CreatePost.js``` component, it doesn't re-render – aasem shoshari Apr 08 '22 at 20:17
  • I think your problem is that you don't have any mechanism to detect when there are new posts (which is different from detecting when `posts` changes). Some other users have given some advice on that, but it isn't clear to me that your application can know precisely when a new post is made. If that's true, your best bet is to simply poll for updates using a timer. [Here's](https://stackoverflow.com/a/69485488/12162258) a previous answer of mine about using timers in an effect. – thisisrandy Apr 08 '22 at 20:19
  • Put another way, your question was, "why do I have an infinite loop?" My answer is to that question. You have another question embedded here, though, which is, "how to I detect when some external service has an update ready?" The answer to that depends on the external service, which you haven't given any details of, so in lieu of further detail, the best answer is, "check periodically for updates." – thisisrandy Apr 08 '22 at 20:21
0

This is not the way we generally handle this. Try this instead:

  1. Store posts using useState. (exactly what you're doing)
const [posts, setPosts] = useState([]);
  1. Fetch posts from the API once using useEffect with an empty array as dependency. This way you can avoid sending infinite requests.
  useEffect(() => {
    const getPosts = async () => {
      const response = id
        ? await fetch(`http://localhost:8000/${id}`)
        : await fetch(`http://localhost:8000/home/${user._id}`);

      const data = await response.json();
      setPosts(
        data.sort((p1, p2) => {
          return new Date(p2.createdAt) - new Date(p1.createdAt);
        })
      );
    };

    getPosts();
  }, []);
  1. When a new post is added, make a new post request to update the database.
  2. Update the local state as well when a new post is added using the data returned by server if the request is successful.
Amit
  • 1,018
  • 1
  • 8
  • 23
  • 1
    and that means i should update ```posts``` state from ```CreatePost.js``` compoenent where i make request to create new post? – aasem shoshari Apr 08 '22 at 20:02
  • Yes, Indeed! Also, you can save server resources by updating the local state as you don't have to fetch all the posts from the server. – Amit Apr 08 '22 at 20:06
  • but how i can do that if there is no connection between these components, i can't send the state as props – aasem shoshari Apr 08 '22 at 20:32
  • To share state between unrelated components, we use the `useContext()` hook along with `useState` hook. Read this article for more information https://dmitripavlutin.com/react-context-and-usecontext/ – Amit Apr 08 '22 at 20:50
0

It infinitely update because the state posts is in your useEffect dependencies, so when you request the API, the state got updated and then when the state got updated, useEffect notice that and call getPosts() again and again

The way you handle this is by removing posts from the dependencies If you want to update the page when the new post is created. You could add another state like [isUpdated, setIsUpdated] and put it in the dependencies instead of posts

and you can setIsUpdated(true) before requesting POST API and setIsUpdated(false) after

like this

function createNewPost() {

setIsUpdated(true)
...
<requesting POST API function>
...
setIsUpdated(false)
}
Bird
  • 63
  • 6