DEV Community

Ayako yk
Ayako yk

Posted on

Avoid Infinite Loop When Using UseMemo with UseEffect

In my previous blog post, I discussed useMemo. I received valuable insights in the comments, pointing out the importance of caution when combining useMemo with useEffect since it could lead to an infinite loop. So, in today's post, I aim to delve deeper into this topic, focusing specifically on when this issue may arise.

useEffect(setup, dependencies?)
The setup is a function and runs every time some values in the dependencies array are changed.

const cachedValue = useMemo(calculateValue, dependencies)
The calculateValue is a function and runs every time some values in the dependencies array are changed, and then the new value is cached.

Let's take a look at when an infinite loop occurs.
Here's an example:

import { useEffect, useMemo, useState } from "react"

export const InfiniteCountUp = () => {
  const [count, setCount] = useState(0)

  const cachedMemo = useMemo(() => count * 2, [count])

  useEffect(() => {
    console.log("Triggered")
  }, [cachedMemo])

  return (
    <div>
      <p>{cachedMemo}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

Every time the Increment button is clicked, the count is updated, which triggers the recalculation of the cachedMemo value due to the dependency on count in the useMemo dependency array. Subsequently, the useEffect is triggered as it depends on the updated cachedMemo value.
This sequence of events could potentially lead to an infinite loop.

Another example is when fetching data.

import { useEffect, useMemo, useState } from "react"

export const InfiniteDataFetching = ({ postId }) => {
  const [post, setPost] = useState("")

  const cachedMemo = useMemo(() => {
    const { postId } = post
    return postId
  }, [post])

  useEffect(() => {
    const fetchData = async () => {
      try {
        const res = await fetch(`https://example.blog/${postId}`)
        const data = await res.json()
        setPost(data)
      } catch (error) {
        console.error(error)
      }
    }

    fetchData()
  }, [postId, cachedMemo])

  return <div>{post && <p>{post.article}</p>}</div>
}
Enter fullscreen mode Exit fullscreen mode

In this example, each time the postId changes, new data is fetched, setting the new post. This setup creates a potential infinite loop: changes in postId trigger useEffect, and the memoized cachedMemo value is recalculated during each render, potentially leading to repeated calls of the effect.

To avoid an infinite loop, it would be better to carefully consider the overall flow. As the code gets larger, it's easy to fix a smaller part without seeing the whole picture. So, taking a step back and understanding the interactions between different parts of the code can help us avoid an infinite loop and decide which dependencies truly need to be included.

Top comments (0)