Passing a function in the useEffect dependency array causes infinite loop

Why is an infinite loop created when I pass a function expression into the useEffect dependency array? The function expression does not alter the component state, it only references it.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>// component has one prop called => sections
const markup = (count) => {
const stringCountCorrection = count + 1;
return (
// Some markup that references the sections prop
);
};
// Creates infinite loop
useEffect(() => {
if (sections.length) {
const sectionsWithMarkup = sections.map((section, index)=> markup(index));
setSectionBlocks(blocks => [...blocks, ...sectionsWithMarkup]);
} else {
setSectionBlocks(blocks => []);
}
}, [sections, markup]);
</code>
<code>// component has one prop called => sections const markup = (count) => { const stringCountCorrection = count + 1; return ( // Some markup that references the sections prop ); }; // Creates infinite loop useEffect(() => { if (sections.length) { const sectionsWithMarkup = sections.map((section, index)=> markup(index)); setSectionBlocks(blocks => [...blocks, ...sectionsWithMarkup]); } else { setSectionBlocks(blocks => []); } }, [sections, markup]); </code>
// component has one prop called => sections

const markup = (count) => {
    const stringCountCorrection = count + 1;
    return (
        // Some markup that references the sections prop
    );
};

// Creates infinite loop
useEffect(() => {
    if (sections.length) {
        const sectionsWithMarkup = sections.map((section, index)=> markup(index));
        setSectionBlocks(blocks => [...blocks, ...sectionsWithMarkup]);
    } else {
        setSectionBlocks(blocks => []);
    }
}, [sections, markup]);

If markup altered state I could understand why it would create an infinite loop but it does not it simply references the sections prop.

So I’m not looking for a code related answer to this question. If possible I’m looking for a detailed explanation as to why this happens.

I’m more interested in the why then just simply finding the answer or correct way to solve the problem.

Why does passing a function in the useEffect dependency array that is declared outside of useEffect cause a re-render when both state and props aren’t changed in said function?

4

The issue is that upon each render cycle, markup is redefined. React uses shallow object comparison to determine if a value updated or not. Each render cycle markup has a different reference. You can use useCallback to memoize the function though so the reference is stable. Do you have the react hook rules enabled for your linter? If you did then it would likely flag it, tell you why, and make this suggestion to resolve the reference issue.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>const markup = useCallback(
(count) => {
const stringCountCorrection = count + 1;
return (
// Some markup that references the sections prop
);
},
[/* any dependencies the react linter suggests */]
);
// No infinite looping, markup reference is stable/memoized
useEffect(() => {
if (sections.length) {
const sectionsWithMarkup = sections.map((section, index) => markup(index));
setSectionBlocks(blocks => [...blocks, ...sectionsWithMarkup]);
} else {
setSectionBlocks([]);
}
}, [sections, markup]);
</code>
<code>const markup = useCallback( (count) => { const stringCountCorrection = count + 1; return ( // Some markup that references the sections prop ); }, [/* any dependencies the react linter suggests */] ); // No infinite looping, markup reference is stable/memoized useEffect(() => { if (sections.length) { const sectionsWithMarkup = sections.map((section, index) => markup(index)); setSectionBlocks(blocks => [...blocks, ...sectionsWithMarkup]); } else { setSectionBlocks([]); } }, [sections, markup]); </code>
const markup = useCallback(
  (count) => {
    const stringCountCorrection = count + 1;
    return (
      // Some markup that references the sections prop
    );
  },
  [/* any dependencies the react linter suggests */]
);

// No infinite looping, markup reference is stable/memoized
useEffect(() => {
  if (sections.length) {
    const sectionsWithMarkup = sections.map((section, index) => markup(index));
    setSectionBlocks(blocks => [...blocks, ...sectionsWithMarkup]);
  } else {
    setSectionBlocks([]);
  }
}, [sections, markup]);

Alternatively if the markup function is only used in the useEffect hook you can move it directly into the hook callback to remove it as an external dependency for the hook.

Example:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>useEffect(() => {
const markup = (count) => {
const stringCountCorrection = count + 1;
return (
// Some markup that references the sections prop
);
};
if (sections.length) {
const sectionsWithMarkup = sections.map((section, index) => markup(index));
setSectionBlocks(blocks => [...blocks, ...sectionsWithMarkup]);
} else {
setSectionBlocks([]);
}
}, [sections, /* any other dependencies the react linter suggests */]);
</code>
<code>useEffect(() => { const markup = (count) => { const stringCountCorrection = count + 1; return ( // Some markup that references the sections prop ); }; if (sections.length) { const sectionsWithMarkup = sections.map((section, index) => markup(index)); setSectionBlocks(blocks => [...blocks, ...sectionsWithMarkup]); } else { setSectionBlocks([]); } }, [sections, /* any other dependencies the react linter suggests */]); </code>
useEffect(() => {
  const markup = (count) => {
    const stringCountCorrection = count + 1;
    return (
      // Some markup that references the sections prop
    );
  };

  if (sections.length) {
    const sectionsWithMarkup = sections.map((section, index) => markup(index));
    setSectionBlocks(blocks => [...blocks, ...sectionsWithMarkup]);
  } else {
    setSectionBlocks([]);
  }
}, [sections, /* any other dependencies the react linter suggests */]);

Additionally, if the markup function has absolutely no external dependencies, i.e. it is a pure function, then it could/should be declared outside any React component.

14

Why is an infinite loop created when I pass a function expression

The “infinite loop” is the component re-rendering over and over because the markup function is a NEW function reference (pointer in memory) each time the component renders and useEffect triggers the re-render because it’s a dependency.

The solution is as @drew-reese pointed out, use the useCallback hook to define your markup function.

1

I faced issues and bugs when I used to add functions into the dependency array of useEffect. I created a simple custom hook that solves the problem of javascript closures (function in useEffect needs point to fresh objects), I separated the use of reactive elements and elements that need to be fresh to have in useEffect dependency array only things that I wanted to react to and still use other state/functions from component without adding it to useEffect dependency array.

hooks.ts

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>export const useObjectRef = <T extends any[]>(...objects: T): React.MutableRefObject<T> => {
const objectsRef = useRef<T>(objects);
objectsRef.current = objects;
return objectsRef;
};
</code>
<code>export const useObjectRef = <T extends any[]>(...objects: T): React.MutableRefObject<T> => { const objectsRef = useRef<T>(objects); objectsRef.current = objects; return objectsRef; }; </code>
export const useObjectRef = <T extends any[]>(...objects: T): React.MutableRefObject<T> => {
    const objectsRef = useRef<T>(objects);
    objectsRef.current = objects;
    return objectsRef;
};

ExampleComponent.tsx

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>export const ExampleComponent: FC = () => {
const [stateA, setStateA] = useState()
const [stateB, setStateB] = useState()
const doSomething = () => {
// Do something
}
// Run only on stateB change
// But still have access to fresh stateA and doSomething function
const objRef = useObjectRef(stateA, doSomething)
useEffect(() => {
const [stateA, doSomething] = objRef.current
doSomething();
console.log(stateA);
}, [objRef, stateB])
return (
... JSX ...
)
}
</code>
<code>export const ExampleComponent: FC = () => { const [stateA, setStateA] = useState() const [stateB, setStateB] = useState() const doSomething = () => { // Do something } // Run only on stateB change // But still have access to fresh stateA and doSomething function const objRef = useObjectRef(stateA, doSomething) useEffect(() => { const [stateA, doSomething] = objRef.current doSomething(); console.log(stateA); }, [objRef, stateB]) return ( ... JSX ... ) } </code>
export const ExampleComponent: FC = () => {
    const [stateA, setStateA] = useState()
    const [stateB, setStateB] = useState()

    const doSomething = () => {
        // Do something
    }
    
    // Run only on stateB change
    // But still have access to fresh stateA and doSomething function
    const objRef = useObjectRef(stateA, doSomething)
    useEffect(() => {
        const [stateA, doSomething] = objRef.current
        doSomething();
        console.log(stateA);
    }, [objRef, stateB])

    return (
        ... JSX ...
    )
}

Maybe this will help someone 🙂

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật