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.toggle
or{()=> this.toggle()}
toggle(){...}
intotoggle = () => {...}
so you don't need tobind
it!