◉RORK LABJP
●GPT-6.1 SOL — Added to Rork's model menu (09-29) with Low, Medium and High effort; included on Pro and Max●SONNET 5.5 — Claude Sonnet 5.5 is selectable: strong at UI design and faster than Sonnet 5 (09-28)●SDK 58 — Beta since 09/15 with no stable date yet; running projects in Expo Go has required login since 09/03●WIDGET — A write-up traces an iOS widget that renders black only in production builds●OBSERVE — EAS Observe now records native crashes too (SDK 57.0.21 or later, 10/07)●NEW — My App Open Ad Only Showed When the App…●GPT-6.1 SOL — Added to Rork's model menu (09-29) with Low, Medium and High effort; included on Pro and Max●SONNET 5.5 — Claude Sonnet 5.5 is selectable: strong at UI design and faster than Sonnet 5 (09-28)●SDK 58 — Beta since 09/15 with no stable date yet; running projects in Expo Go has required login since 09/03●WIDGET — A write-up traces an iOS widget that renders black only in production builds●OBSERVE — EAS Observe now records native crashes too (SDK 57.0.21 or later, 10/07)●NEW — My App Open Ad Only Showed When the App…
Articles/Dev Tools
⬡ Dev Tools/2026-04-20Intermediate

Why useEffect Loops Infinitely in Rork-Generated Code — and How to Fix It

Rork-generated React Native code can trigger useEffect infinite loops through subtle dependency array mistakes. This guide covers 5 common causes — stale deps, object references, unstable callbacks — with Before/After code fixes.

useEffectinfinite loopReact Native239debugging13troubleshooting69Rork585Expo219

If you have ever launched a Rork-generated app only to watch your console flood with repeated log entries and the UI grind to a halt, you have hit a useEffect infinite loop. The code looks structurally correct, yet something keeps triggering a re-render cycle that never stops.

AI-generated code is particularly prone to this problem. The patterns are subtle — a dependency array that includes the value being updated, an object that gets recreated on every render, a callback function passed as a prop that never stabilises. Once you know the five root causes, debugging becomes quick and methodical.

What an Infinite Loop Actually Looks Like

React Native runs useEffect after every render where one of the values in the dependency array has changed. When the effect itself triggers a state update that is also in the dependency array, you get a self-reinforcing cycle:

  1. Component renders
  2. useEffect runs
  3. setState is called inside the effect
  4. Component re-renders due to state change
  5. Dependency array value has changed — effect runs again
  6. Back to step 3 (infinite)

This degrades performance, drains battery, and can crash the app entirely on lower-end devices. Here are the five patterns that cause it in Rork projects.

The 5 Patterns That Cause Infinite Loops

Pattern 1: State That Is Updated Inside the Effect Is Also a Dependency

This is the most common mistake in Rork-generated code.

// Bad — setting data triggers a re-render, which triggers the effect again
const [data, setData] = useState([]);
 
useEffect(() => {
  fetchData().then((result) => {
    setData(result); // updates data...
  });
}, [data]); // ...but data is in the dep array

The fix is to remove the state variable you are updating from the dependency array, and only include the values that should trigger a new fetch.

// Good — runs once on mount, no loop
const [data, setData] = useState([]);
 
useEffect(() => {
  fetchData().then((result) => {
    setData(result);
  });
}, []); // empty array = run only on mount

If you need to refetch when something changes, use a trigger variable — not the state being updated:

// Good — refetch when userId changes, not when data changes
useEffect(() => {
  fetchData(userId).then(setData);
}, [userId]); // userId is the trigger, not data

Pattern 2: Objects or Arrays in the Dependency Array

React uses reference equality (===) to compare dependency values. Objects and arrays are recreated on every render, so even if the content is identical, React sees them as new values.

// Bad — config is a new object reference on every render
const config = { userId: "123", limit: 20 };
 
useEffect(() => {
  fetchUserData(config);
}, [config]); // always "new", always loops

The cleanest fix is to decompose the object into primitive values:

// Good — primitive values use value comparison, not reference
const userId = "123";
const limit = 20;
 
useEffect(() => {
  fetchUserData({ userId, limit });
}, [userId, limit]);

Alternatively, if the object must exist as a variable, memoize it with useMemo:

import { useMemo } from "react";
 
const config = useMemo(() => ({ userId, limit }), [userId, limit]);
 
useEffect(() => {
  fetchUserData(config);
}, [config]); // stable reference now

Pattern 3: Functions Defined Inside the Component Are in the Dependency Array

Functions are also compared by reference. A function defined inside the component body is a new object on every render.

// Bad — loadData is recreated on every render
const loadData = () => {
  fetchData().then(setData);
};
 
useEffect(() => {
  loadData();
}, [loadData]); // always new reference — loops

Two solutions: move the function outside the component, or memoize it with useCallback.

// Good option 1 — move outside the component entirely
const loadData = (setData) => {
  fetchData().then(setData);
};
 
const MyComponent = () => {
  const [data, setData] = useState([]);
  useEffect(() => {
    loadData(setData);
  }, []); // setData from useState is stable
};
// Good option 2 — useCallback inside the component
import { useCallback } from "react";
 
const MyComponent = () => {
  const [data, setData] = useState([]);
  
  const loadData = useCallback(() => {
    fetchData().then(setData);
  }, []); // stable reference
 
  useEffect(() => {
    loadData();
  }, [loadData]);
};

Pattern 4: Blindly Adding Everything the Linter Warns About

The ESLint exhaustive-deps rule will warn when values used inside a useEffect are missing from the dependency array. The tempting fix is to add everything the linter suggests — which can create a loop if one of those values is set inside the same effect.

Before adding a value to the dependency array, ask: "Do I actually want the effect to re-run when this value changes?"

  • Yes → add it to the array, but verify it does not cause a state update cycle
  • No → either keep it out (if safe), or use useRef to access the current value without triggering re-runs
// useRef gives you the latest value without adding it to the dep array
import { useRef } from "react";
 
const MyComponent = ({ onSuccess }) => {
  const onSuccessRef = useRef(onSuccess);
  
  useEffect(() => {
    onSuccessRef.current = onSuccess;
  }); // no dep array = sync on every render (intentional)
 
  useEffect(() => {
    doAsyncWork().then(() => {
      onSuccessRef.current(); // call via ref
    });
  }, []); // no loop — onSuccess is not in the dep array
};

Pattern 5: Unstable Callback Props From a Parent

Rork often generates child components that call a parent-supplied callback inside useEffect. If the parent creates the function inline, it is unstable.

// Bad — parent creates a new function on every render
const Parent = () => {
  return (
    <Child onDataLoaded={(data) => console.log(data)} />
    // ↑ new function reference every render
  );
};
 
const Child = ({ onDataLoaded }) => {
  useEffect(() => {
    fetchData().then(onDataLoaded);
  }, [onDataLoaded]); // loops because onDataLoaded keeps changing
};

Fix the parent with useCallback:

import { useCallback } from "react";
 
const Parent = () => {
  const handleDataLoaded = useCallback((data) => {
    console.log(data);
  }, []); // stable reference
 
  return <Child onDataLoaded={handleDataLoaded} />;
};

How to Debug a Loop Systematically

When you are not sure which effect is looping, use this three-step approach.

Step 1: Add a timestamp log. If the same timestamp floods the console, that is your effect.

useEffect(() => {
  console.log("effect ran at:", Date.now());
}, [userId, data]);

Step 2: Log what changed. Build a simple prev-value tracker:

import { useRef, useEffect } from "react";
 
const prevRef = useRef({});
 
useEffect(() => {
  const keys = ["userId", "data"];
  keys.forEach((key) => {
    if (prevRef.current[key] \!== eval(key)) {
      console.log(`${key} changed:`, prevRef.current[key], "->", eval(key));
    }
  });
  prevRef.current = { userId, data };
}, [userId, data]);

Step 3: Comment out setState calls one by one. Disable each state update inside the effect until the loop stops. That is your culprit.

Prompting Rork to Fix the Loop

When asking Rork AI to resolve this, be specific:

My useEffect is running in an infinite loop. The effect calls
setData(result) but data is also in the dependency array, which
causes it to re-run on every state update.

Please fix this so the fetch only runs on mount (or when userId
changes). Do not suppress the ESLint warning with a disable
comment — fix the dependency array correctly.

Here is the component:
[paste your code]

Explicitly telling Rork not to use eslint-disable is important. AI assistants will sometimes silence the warning rather than address the root cause.

The Rule of Thumb

When you suspect an infinite loop, check the dependency array for three things:

  • Is the state variable being updated inside the effect also listed as a dependency?
  • Are any objects, arrays, or functions in the dependency array that are recreated on every render?
  • Is a callback prop included that the parent creates inline?

Resolving any of these with useCallback, useMemo, useRef, or by restructuring the logic will stop the loop. For deeper patterns around state architecture in Rork apps, see Advanced State Management Patterns for Rork Apps. For broader performance issues, Rork App Performance Optimization Complete Guide covers profiling and memory management as well.

Share

Thank You for Reading

Rork Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • ✦Copy-paste ready implementation code
  • ✦New advanced guides published daily
  • ✦$5/mo or $10 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

⬡ Dev Tools2026-10-07
Japanese Headings Won't Turn Bold on Android: How to Trace and Fix a fontWeight That Does Nothing
A troubleshooting note for Rork apps where headings look bold on iPhone but stay thin on Android. How to tell the symptoms apart, pick weights through fontFamily, and verify the fix.
⬡ Dev Tools2026-09-04
EAS secret visibility does not keep a value out of your app — deciding prefix and visibility separately
The EXPO_PUBLIC_ prefix decides what ships inside your app; EAS visibility decides who can read it. Why stacking them blanks a value on OTA updates, and how to check your build.
⬡ Dev Tools2026-08-22
Every bulk replace exited zero. The damage was in the lines I did not delete
Run a bulk replace over generated code and the breakage lands on the neighbouring lines, not the matched ones. Here is what broke in a live project, and a dependency-free guard that checks the invariants a replace must preserve.