Infinite Loop در React معمولا زمانی اتفاق می‌افتد که useEffect مدام باعث تغییر State شود و هر تغییر دوباره کامپوننت را Render کند. این مشکل بیشتر به خاطر Dependency اشتباه، تغییر مداوم State یا استفاده نادرست از Object و Function داخل useEffect رخ می‌دهد. اگر دلیل اصلی Loop را پیدا نکنید، پروژه ممکن است کند شود، API چندین بار صدا زده شود یا حتی مرورگر Crash کند.

useEffect در React چه کاری انجام می‌دهد؟

هوک useEffect برای اجرای Side Effect ها در React استفاده می‌شود.

مثلا:

  • دریافت داده از API
  • اجرای Timer
  • مدیریت Event Listener
  • تغییر Title صفحه

نمونه ساده:

useEffect(() => {
  console.log("Component Mounted")
}, [])

در این مثال، Effect فقط یک بار بعد از Render اولیه اجرا می شود.

Infinite Loop در React چیست؟

Infinite Loop یعنی کامپوننت مدام Render شود و هیچ وقت متوقف نشود.

مثلا:

  • State تغییر می‌کند
  • Render جدید اتفاق می‌افتد
  • useEffect دوباره اجرا می‌شود
  • دوباره State تغییر می‌کند

و این چرخه تا بی نهایت ادامه پیدا می‌کند.

نتیجه معمولا:

  • کند شدن شدید صفحه
  • مصرف بالای RAM
  • Crash شدن مرورگر
  • درخواست‌های تکراری API

است.

چرا useEffect باعث Infinite Loop می‌شود؟

دلایل مختلفی وجود دارد اما بعضی اشتباهات بسیار رایج‌تر هستند.

نداشتن Dependency Array

یکی از رایج‌ترین دلایل Infinite Loop همین است.

مثلا:

useEffect(() => {
  fetchData()
})

در این حالت useEffect بعد از هر Render اجرا می‌شود.

اگر داخل fetchData تغییر State داشته باشید، کامپوننت دوباره Render می شود و Loop شکل می‌گیرد.

روش درست:

useEffect(() => {
  fetchData()
}, [])

حالا فقط یک بار اجرا می‌شود.

تغییر State داخل useEffect

این مورد خیلی رایج است:

useEffect(() => {
  setCount(count + 1)
}, [count])

اینجا:

  • count تغییر می‌کند
  • useEffect دوباره اجرا می‌شود
  • دوباره count تغییر می‌کند

و Loop بی نهایت ایجاد می‌شود.

استفاده اشتباه از Object و Array

بعضی وقت‌ها Dependency ها در ظاهر ثابت هستند اما React آن ها را جدید در نظر می‌گیرد.

مثلا:

const user = {name: "Ali"}

useEffect(() => {
  console.log(user)
}, [user])

در هر Render یک Object جدید ساخته می‌شود.

در نتیجه useEffect دوباره اجرا می‌شود.

استفاده اشتباه از Function داخل Dependency

مثلا:

const getData = () => {
  console.log("fetch")
}

useEffect(() => {
  getData()
}, [getData])

چون Function در هر Render دوباره ساخته می‌شود، Effect هم دوباره اجرا می‌شود.

روش حل مشکل Function Dependency

می‌توانید از useCallback استفاده کنید:

const getData = useCallback(() => {
  console.log("fetch")
}, [])

حالا React Function را ثابت نگه می‌دارد.

صدا زدن API داخل Loop

یکی از بدترین حالت‌ها این است که Infinite Loop باعث Request های بی پایان شود.

مثلا:

useEffect(() => {
  fetch('/api/data')
    .then(res => res.json())
    .then(data => setData(data))
}, [data])

اینجا:

  • setData باعث تغییر data می‌شود
  • useEffect دوباره اجرا می‌شود
  • API دوباره صدا زده می‌شود

و سرور ممکن است کاملا تحت فشار قرار بگیرد.

نشانه‌های Infinite Loop در React

معمولا این مشکل با یکی از این نشانه‌ها مشخص می‌شود:

  • مرورگر کند می‌شود
  • فن لپ تاپ شدید کار می‌کند
  • API مدام Request می‌گیرد
  • Console پر از Log تکراری می‌شود
  • React Error نمایش داده می‌شود

مثلا:

Maximum update depth exceeded

این خطا یعنی کامپوننت بیش از حد Render شده است.

چگونه Infinite Loop را پیدا کنیم؟

استفاده از Console.log

ساده‌ترین روش:

console.log("render")

اگر مدام تکرار شود یعنی Loop دارید.

استفاده از React DevTools

داخل Profiler می‌توانید ببینید:

  • چه چیزی Render شده
  • چند بار Render شده
  • دلیل Render چه بوده

این ابزار برای Debug خیلی مهم است.

بررسی Dependency Array

بیشتر Loop ها از همین قسمت شروع می‌شوند.

باید بررسی کنید:

  • آیا Dependency واقعا لازم است؟
  • آیا مقدار مدام تغییر می‌کند؟
  • آیا Object یا Function جدید ساخته می‌شود؟

روش اصولی حل Infinite Loop در React

Dependency ها را درست مدیریت کنید

همیشه Dependency واقعی را قرار دهید.

مثلا:

useEffect(() => {
  fetchUsers()
}, [])

این باعث می‌شود Effect فقط یک بار اجرا شود.

از useMemo استفاده کنید

برای Object یا Array:

const config = useMemo(() => {
  return {theme: 'dark'}
}, [])

این کار از ساخت Object جدید جلوگیری می‌کند.

از useCallback استفاده کنید

برای Function ها:

const fetchData = useCallback(() => {
  console.log("data")
}, [])

این روش جلوی Render اضافی را می‌گیرد.

State غیر ضروری نسازید

هر State جدید یعنی احتمال Render بیشتر.

گاهی داده اصلا نیاز به State ندارد.

اشتباهات رایج برنامه نویسان React

قرار دادن State داخل Dependency بدون نیاز

مثلا:

useEffect(() => {
  loadData()
}, [data])

در حالی که data هیچ ارتباطی با Effect ندارد.

ساخت Object داخل JSX

مثلا:

<Component style={{color: 'red'}} />

در پروژه های بزرگ این کار باعث Render اضافی می شود.

استفاده زیاد از Global State

بعضی پروژه‌ها همه چیز را داخل Redux یا Context ذخیره می‌کنند.

نتیجه:

  • Render زیاد
  • مصرف RAM بالا
  • Performance ضعیف

روش جلوگیری از Infinite Loop در React

کامپوننت‌ها را کوچک نگه دارید

کامپوننت‌های بزرگ:

  • Debug سخت‌تری دارند
  • Loop های پیچیده‌تری ایجاد می کنند

Effect های پروژه را ساده نگه دارید

هر useEffect باید فقط یک وظیفه مشخص داشته باشد.

Effect های شلوغ معمولا مشکل ساز می‌شوند.

قبل از اضافه کردن Dependency فکر کنید

هر Dependency جدید می‌تواند باعث Render دوباره شود.

نباید بدون دلیل Dependency اضافه شود.

از ESLint Plugin React Hooks استفاده کنید

این ابزار خیلی از اشتباهات مربوط به useEffect را پیدا می‌کند.

نصب:

npm install eslint-plugin-react-hooks

جمع بندی

Infinite Loop در React معمولا به خاطر استفاده اشتباه از useEffect، تغییر مداوم State یا مدیریت نادرست Dependency ها اتفاق می افتد. بیشتر این مشکلات با استفاده درست از Dependency Array، useMemo و useCallback قابل حل هستند. مهم‌ترین نکته این است که دقیقا بدانید چه چیزی باعث Render دوباره کامپوننت می‌شود تا بتوانید Loop را کنترل کنید.