Recently I got this error:
Error: Minified React error #185; visit https://reactjs.org/docs/error-decoder.html?invariant=185 for the full message or use the non-minified dev environment for full errors and additional helpful warnings.
The full text of the error you just encountered is:
Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.
Ok. Here is my case, I use react function component + react hooks. Let's see the incorrect sample code first:
import { useEffect, useState } from "react";
const service = {
makeInfo(goods) {
if (!goods) return { channel: "" };
return { channel: goods.channel };
},
getGoods() {
return new Promise((resolve) => {
setTimeout(() => {
resolve({
channel: "so",
id: 1,
banners: [{ payway: "visa" }, { payway: "applepay" }]
});
}, 1000);
});
},
makeBanners(info, goods) {
if (!goods) return [];
return goods.banners.map((v) => {
return { ...v, payway: v.payway.toUpperCase() };
});
}
};
export default function App() {
const [goods, setGoods] = useState();
const [banners, setBanners] = useState([]);
useEffect(() => {
service.getGoods().then((res) => {
setGoods(res);
});
}, []);
const info = service.makeInfo(goods);
useEffect(() => {
console.log("[useEffect] goods: ", goods);
if (!goods) return;
setBanners(service.makeBanners({}, goods));
}, [info, goods]);
return <div>banner count: {banners.length}</div>;
}
service - process API call, and has some methods for converting DTO data view model. It has nothing to do with React. Maybe you have a service like this in your project.
My logic is that the banners view model constructs from the goods data returned from the API.
useEffect({...}, [info, goods]) has two dependencies: info and goods.
When info and goods change, useEffect hook will re-execute, set banners view model, it looks good, right?
No! It will cause a memory leak. The useEffect hook will execute infinitely. Why?
Because when setBanner() executed, the component will re-render, the const info = service.makeInfo(goods); statement will execute again, returns a new info object, this will lead to the change of the useEffect's deps, causing useEffect to execute again, forming a dead cycle.
Solutions: use useMemo returns a memorized value. Use this memorized value as the dependency of the useEffect hook.
// ...
const info = useMemo(() => {
return service.makeInfo(goods);
}, [goods]);
useEffect(() => {
console.log("[useEffect] goods: ", goods);
if (!goods) return;
setBanners(service.makeBanners({}, goods));
}, [info, goods]);
//...
Codesandbox
this.toggle()tothis.toggleor{()=> this.toggle()}toggle(){...}intotoggle = () => {...}so you don't need tobindit!