Reputation: 3347
I am trying to toggle the state of a component in ReactJS but I get an error stating:
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.
I don't see the infinite loop in my code, can anyone help?
ReactJS component code:
import React, { Component } from 'react';
import styled from 'styled-components';
class Item extends React.Component {
constructor(props) {
super(props);
this.toggle= this.toggle.bind(this);
this.state = {
details: false
}
}
toggle(){
const currentState = this.state.details;
this.setState({ details: !currentState });
}
render() {
return (
<tr className="Item">
<td>{this.props.config.server}</td>
<td>{this.props.config.verbose}</td>
<td>{this.props.config.type}</td>
<td className={this.state.details ? "visible" : "hidden"}>PLACEHOLDER MORE INFO</td>
{<td><span onClick={this.toggle()}>Details</span></td>}
</tr>
)}
}
export default Item;
Upvotes: 304
Views: 581477
Reputation: 1630
In this case, the following code:
{<td><span onClick={this.toggle()}>Details</span></td>}
causes toggle function to call immediately and re-renders it again and again, thus making infinite calls.
So, passing only the reference to that toggle method will solve the problem.
i.e.
{<td><span onClick={this.toggle}>Details</span></td>}
will be the solution code.
If you want to use the (), you should use an arrow function like this
{<td><span onClick={()=> this.toggle()}>Details</span></td>}
In case you want to pass parameters, you should choose the last option and you can pass parameters as follows:
{<td><span onClick={(arg)=>this.toggle(arg)}>Details</span></td>}
In the last case, it doesn't call the function immediately, so it won't re-render the function, hence avoiding infinite calls.
Upvotes: 8
Reputation: 18630
That is because you are calling toggle inside the render method which will cause to re-render and toggle will call again and re-rendering again and so on.
This line in your code:
{<td><span onClick={this.toggle()}>Details</span></td>}
You need to make onClick
refer to this.toggle
instead of calling it.
To fix the issue do this:
{<td><span onClick={this.toggle}>Details</span></td>}
Upvotes: 412
Reputation: 1077
Just remove the () but if it the case of useEffect then
const [isInitialRender, setIsInitialRender] = useState(true);
useEffect(() => {
const data = localStorage.getItem("auth");
if(isInitialRender){
setIsInitialRender(false);
if (data) {
const parsed = JSON.parse(data);
setAuth({...auth, user: parsed.user, token: parsed.token });
}
}
}, [auth, isInitialRender]);
isInitialRender true and false will avoid you to fall into loop
Upvotes: 0
Reputation: 1388
in my case it was happening due to infinite loop.
my one function was setting state and the component was getting reload and that component in turn was causing function to re run and then function was again setting the state.. and the loop was going on to infinity..
to avoid this situation i wrapped my function in useEffect
hook and gave it some dependency so that this function run only if the dependency gets changed.
Upvotes: 0
Reputation: 19
when i use useEffect like this useEffect(() =>{})
, add [] to it, like this useEffect(() => {},[])
.
Upvotes: 0
Reputation: 1938
Many good answer but all missing some example considering hooks in react/ React native
As wrote in an answer above is that the callback should not be "called" inside the child Component but only referenced.
What this means? Let's consider a Parent Component that take 2 Children Components that change rgb color for change color on press:
import React, { useState } from "react"
import {View, Text, StyleSheet } from "react-native"
import ColorCounter from "../components/ColorCounter"
const SquareScreen = () =>{
const [red, setRed] = useState(0)
const [blue, setBlue] = useState(0)
const [green, setGreen] = useState(0)
return (
<View>
<ColorCounter
onIncrease={() => setRed(red + 15)}
onDecrease={() => setRed(red - 15)}
color="Red"
/>
<ColorCounter
onIncrease={() => setBlue(blue + 15)}
onDecrease={() => setBlue(blue - 15)}
color="Blue"
/>
<ColorCounter
onIncrease={() => setGreen(green + 15)}
onDecrease={() => setGreen(green - 15)}
color="Green"
/>
<View
style={{
height:150,
width:150,
backgroundColor:`rgb(${red},${blue},${green})`
}}
/>
</View>
)
}
const styles = StyleSheet.create({})
export default SquareScreen
This is the Child Button Component:
import React, { useState } from "react"
import {View, Text, StyleSheet, Button } from "react-native"
const ColorCounter = ({color, onIncrease, onDecrease}) =>{
return (
<View>
<Text>{color}</Text>
<Button onPress={onIncrease} title={`Increase ${color}`} /> --> here if you use onPress={onIncrease()} this would cause a call of setColor(either setRed,SetBlue or setGreen) that call again onIncrease and so on in a loop)
<Button onPress={onDecrease} title={`Decrease ${color}`} />
</View>
)
}
export default ColorCounter
Upvotes: 0
Reputation: 102577
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
orcomponentDidUpdate
. 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]);
//...
Upvotes: 2
Reputation: 239
1.If we want to pass argument in the call then we need to call the method like below
As we are using arrow functions no need to bind the method in constructor
.
onClick={() => this.save(id)}
when we bind the method in constructor like this
this.save= this.save.bind(this);
then we need to call the method without passing any argument like below
onClick={this.save}
and we try to pass argument while calling the function as shown below then error comes like maximum depth exceeded.
onClick={this.save(id)}
Upvotes: 8
Reputation: 3207
I know this has plenty of answers but since most of them are old (well, older), none is mentioning approach I grow very fond of really quick. In short:
Use functional components and hooks.
In longer:
Try to use as much functional components instead class ones especially for rendering, AND try to keep them as pure as possible (yes, data is dirty by default I know).
Two bluntly obvious benefits of functional components (there are more):
Quick proof for 2nd point - Isn't this absolutely disgusting?
constructor(props) {
super(props);
this.toggle= this.toggle.bind(this);
this.state = {
details: false
}
}
If you are using functional components for more then rendering you are gonna need the second part of great duo - hooks. Why are they better then lifecycle methods, what else can they do and much more would take me a lot of space to cover so I recommend you to listen to the man himself: Dan preaching the hooks
In this case you need only two hooks:
A callback hook conveniently named useCallback
. This way you are preventing the binding the function over and over when you re-render.
A state hook, called useState
, for keeping the state despite entire component being function and executing in its entirety (yes, this is possible due to magic of hooks). Within that hook you will store the value of toggle.
If you read to this part you probably wanna see all I have talked about in action and applied to original problem. Here you go: Demo
For those of you that want only to glance the component and WTF is this about, here you are:
const Item = () => {
// HOOKZ
const [isVisible, setIsVisible] = React.useState('hidden');
const toggle = React.useCallback(() => {
setIsVisible(isVisible === 'visible' ? 'hidden': 'visible');
}, [isVisible, setIsVisible]);
// RENDER
return (
<React.Fragment>
<div style={{visibility: isVisible}}>
PLACEHOLDER MORE INFO
</div>
<button onClick={toggle}>Details</button>
</React.Fragment>
)
};
PS: I wrote this in case many people land here with similar problem. Hopefully, they will like what I have shown here, at least well enough to google it a bit more. This is NOT me saying other answers are wrong, this is me saying that since the time they have been written, there is another way (IMHO, a better one) of dealing with this.
Upvotes: 14
Reputation: 49
onClick you should call function, thats called your function toggle.
onClick={() => this.toggle()}
Upvotes: 2
Reputation: 4463
ReactJS: Maximum update depth exceeded error
inputDigit(digit){
this.setState({
displayValue: String(digit)
})
<button type="button"onClick={this.inputDigit(0)}>
why that?
<button type="button"onClick={() => this.inputDigit(1)}>1</button>
The function onDigit sets the state, which causes a rerender, which causes onDigit to fire because that’s the value you’re setting as onClick which causes the state to be set which causes a rerender, which causes onDigit to fire because that’s the value you’re… Etc
Upvotes: 4
Reputation: 794
Forget about the react first:
This is not related to react and let us understand the basic concepts of Java Script. For Example you have written following function in java script (name is A).
function a() {
};
Q.1) How to call the function that we have defined?
Ans: a();
Q.2) How to pass reference of function so that we can call it latter?
Ans: let fun = a;
Now coming to your question, you have used paranthesis with function name, mean that function will be called when following statement will be render.
<td><span onClick={this.toggle()}>Details</span></td>
Then How to correct it?
Simple!! Just remove parenthesis. By this way you have given the reference of that function to onClick event. It will call back your function only when your component is clicked.
<td><span onClick={this.toggle}>Details</span></td>
One suggestion releated to react:
Avoid using inline function as suggested by someone in answers, it may cause performance issue.
Avoid following code, It will create instance of same function again and again whenever function will be called (lamda statement creates new instance every time).
Note: and no need to pass event (e) explicitly to the function. you can access it with in the function without passing it.
{<td><span onClick={(e) => this.toggle(e)}>Details</span></td>}
https://cdb.reacttraining.com/react-inline-functions-and-performance-bdff784f5578
Upvotes: 51
Reputation: 3159
if you don't need to pass arguments to function, just remove () from function like below:
<td><span onClick={this.toggle}>Details</span></td>
but if you want to pass arguments, you should do like below:
<td><span onClick={(e) => this.toggle(e,arg1,arg2)}>Details</span></td>
Upvotes: 9
Reputation: 439
You should pass the event object when calling the function :
{<td><span onClick={(e) => this.toggle(e)}>Details</span></td>}
If you don't need to handle onClick event you can also type :
{<td><span onClick={(e) => this.toggle()}>Details</span></td>}
Now you can also add your parameters within the function.
Upvotes: 43