let effectCount = 0;
function simulateRender(getOptions, effect) {
// Simulates component rendering 5 times with deps check
let prevDeps = null;
for (let i = 0; i < 5; i++) {
const options = getOptions(); // gets deps
const changed = !prevDeps || options !== prevDeps;
if (changed) {
effect();
effectCount++;
}
prevDeps = options;
}
}
// Bug: getOptions creates a new object every "render"
simulateRender(
() => ({ timeout: 5000, retries: 3 }), // new {} each call!
() => {} // fetch effect
);
console.log(effectCount); // should be 1, is 5let effectCount = 0;
function simulateRender(getOptions, effect) {
let prevDeps = null;
for (let i = 0; i < 5; i++) {
const options = getOptions();
const changed = !prevDeps || options !== prevDeps;
if (changed) {
effect();
effectCount++;
}
prevDeps = options;
}
}
// Fix: stable reference — created once outside the render
const stableOptions = { timeout: 5000, retries: 3 };
simulateRender(
() => stableOptions, // same reference every time
() => {}
);
console.log(effectCount);Bug: An inline object literal {} creates a new reference on every call. Each render gets a new options object, so it's never === prevDeps, so the effect runs every time.
Explanation: stableOptions is the same reference on every render. The first render detects a change (null !== stableOptions), effect runs once. Subsequent renders: stableOptions === stableOptions, skip.
Key Insight: Never put inline objects/arrays in useEffect deps: useEffect(() => {}, [{}]) — infinite loop. Either move them outside the component, inside the effect, or wrap in useMemo.