class Suggestions extends React.Component {
constructor(props) {
super(props);
this.state = {
visible: false
}
this.handleClick = this.handleClick.bind(this);
}
handleClick(event) {
console.log(event.target.value)
}
componentDidUpdate(props, state) {
if(props.dataSearchBox) {
this.setState({visible: true})
} else {
this.setState({visible: false})
}
}
render() {
return (
<div className={`g${this.state.visible === true ? '': ' h'}`}>
<ul>
</ul>
</div>
);
}
}
export default Suggestions;
I am trying to set the state based on the props change, however I get the following error inside componentDidUpdate:
There was an error! Error: 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.
What am I doing wring here?
Your problem is here:
componentDidUpdate(props, state) {
if(props.dataSearchBox) {
this.setState({visible: true})
} else {
this.setState({visible: false})
}
}
componentDidUpdate gets called, it sets the state, which triggers componentDidUpdate... round and round.
Do you need a separate state property to handle this? That's a bit of an anti-pattern - you have what you need already as a prop, just use its value to control the visibility directly:
render() {
return (
<div className={`g${this.props.dataSearchBox ? '': ' h'}`}>
<ul>
</ul>
</div>
);
}
Related
I know this can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate but in my situation even if I call setState just one time after that I got this error. Here is my state and componentDidUpdate:
constructor(props) {
super(props);
this.state = { usernameAlreadyUsing: false };
}
componentDidUpdate() {
if(this.props.usernames.includes(this.props.username)) {
this.setState({ usernameAlreadyUsing: true });
}
}
Every time setState called componentDidUpdate recalls itself. So
this situation causes you to enter an endless loop.
Here try this,
constructor(props) {
super(props);
this.state = { usernameAlreadyUsing: false };
}
componentDidUpdate() {
const { usernameAlreadyUsing } = this.state;
if(this.props.usernames.includes(this.props.username) && !usernameAlreadyUsing) {
this.setState({ usernameAlreadyUsing: true });
}
}
With this implementation your code only enters one time in componentDidUpdate.
Hope it works.
constructor(props) {
super(props);
this.state = { usernameAlreadyUsing: false };
}
componentDidUpdate(prevProps) {
if(this.props.usernames.length != prevProps.usernames.length) {
if(this.props.usernames.includes(this.props.username)) {
this.setState({ usernameAlreadyUsing: true });
}
}
}
try. this, problem with your above is , once its setting username is already present, its again calling setState as the condition succeeds every time. try this
Hope it helps.feel free for doubts
I have the following react component, which contains the state signed_in.
When the login state changes, the callback fires (verified using the console logging), but the component does not re-render.
class Login extends React.Component {
constructor(props) {
super(props);
this.state = {};
auth.onAuthStateChanged(function(user) {
if (user) {
this.state = { signed_in: true };
console.log("signed in");
} else {
this.state = { signed_in: false };
console.log("signed out");
}
});
}
render() {
return (
<MDBContainer className="text-center mt-5 pt-5">
<div>
{this.state.signed_in ? (
<div>
<h5>Please sign-in:</h5>
<StyledFirebaseAuth uiConfig={uiConfig} firebaseAuth={auth} />
</div>
) : (
<h5>You are already signed in.</h5>
)}
</div>
</MDBContainer>
);
}
}
I suspect this may be because the callback function isn't modifying the components state (this.state)? What is the fix here?
In the case of your class based component, a re-render is triggered by calling the components setState() method.
The setState() method accepts an object describing the state change that will be applied to your components state:
/* Update the signed_in field of your components state to true */
this.setState({ signed_in: true });
By calling setState() React internally applies the state change to the existing component state and then triggers a re-render, at which point any state changes that have been made will be visible in your components the subsequent render cycle.
In the case of your code, one way to achieve these changes would be:
class Login extends React.Component {
constructor(props) {
super(props);
this.state = {};
/* Make arrow function, allowing component's
setState method to be accessible via "this" */
auth.onAuthStateChanged((user) => {
if (user) {
/* Pass state change to setState() */
this.setState({ signed_in: true });
console.log("signed in");
} else {
/* Pass state change to setState() */
this.state = { signed_in: false };
console.log("signed out");
}
});
}
render() {
return (
<MDBContainer className="text-center mt-5 pt-5">
<div>
{this.state.signed_in ? (
<div>
<h5>Please sign-in:</h5>
<StyledFirebaseAuth uiConfig={uiConfig} firebaseAuth={auth} />
</div>
) : (
<h5>You are already signed in.</h5>
)}
</div>
</MDBContainer>
);
}
}
I've recently seen this type of react pattern where the state is being set in a render by using this.state:
class ShowMe extends React.Component {
constructor(props) {
super(props);
this.state = {
showButton: false,
};
}
render() {
if (this.props.show) {
this.state.showButton = true; //setting state in render!!
}
return (
<div>
<div> Show or hide button </div>
{this.state.showButton && <Button content='Btn'/>}
</div>
)
}
}
This seems like an anti-pattern. Can this cause bugs? It seems to work properly though.
I would just use a component lifecycle to set the state:
class ShowMe extends React.Component {
constructor(props) {
super(props);
this.state = {
showButton: false,
};
}
componentWillReceiveProps(nextProps) {
if(nextProps.show) {
this.setState({
showButton: true,
})
}
}
render() {
return (
<div>
<div> Show or hide button </div>
{this.state.showButton && <Button content='Btn'/>}
</div>
)
}
}
What is the recommended way?
render should always be pure without any side effects, so it's certainly a bad practice.
from the React docs :
The render() function should be pure, meaning that it does not modify component state, it returns the same result each time it’s invoked, and it does not directly interact with the browser. If you need to interact with the browser, perform your work in componentDidMount() or the other lifecycle methods instead. Keeping render() pure makes components easier to think about.
Take a look also here and here.
It is an anti-pattern. If showButton state is not always equal to show props (which is the case in the example), I would use this:
class ShowMe extends React.Component {
constructor(props) {
super(props);
this.state = {
showButton: this.props.show,
};
}
componentDidUpdate(prevProps, prevState) {
prevProps.show !== this.props.show && this.setState({showButton: this.props.show})
}
render() {
return (
<div>
<div> Show or hide button </div>
{this.state.showButton && <Button content='Btn'/>}
</div>
)
}
}
Edit: As of React 16.3 one should use getDerivedStateFromProps in this case.
Note that componentWillReceiveProps will be deprecated.
From the docs: getDerivedStateFromProps is invoked after a component is instantiated as well as when it receives new props. It should return an object to update state, or null to indicate that the new props do not require any state updates.
https://reactjs.org/docs/react-component.html#static-getderivedstatefromprops
It is incorrect setting state in render method. You can set state in lifecyles method. But other thing is that your component can receive same props many times, so your component will be set state many times, and renderd. To solve this problem you need to compare your new with your current props for example compare json objects:
componentWillReceiveProps(nextProps) {
if(JSON.stringify(this.props) !== JSON.stringify(nextProps) && nextProps.show) {
this.setState({
showButton: true,
})
}
}
or use PureComponent. And that garentee you that your component will not rerendered constantly.
And it will be better if you do not rerender component if state.showButton currently seted to true.
componentWillReceiveProps(nextProps) {
if(JSON.stringify(this.props) !== JSON.stringify(nextProps) && nextProps.show) {
if(!this.state.showButton) {
this.setState({
showButton: true,
})
}
}
}
In my React app i have components structure:
-AllElements
--SingleElement
--SingleElementDetails
I am passing method See to SingleElement component where I invoke seefunc to invoke see method from AllElements component. The problem i my state (name) in AllElements not change after first onClick trigger, it changes after secund click. Could you tell my why ?
class AllElements extends Component {
constructor(props) {
super(props);
this.state = {
myData: [],
viewingElement: {
name:""
}
}
this.see = this.see.bind(this);
console.log('Initial Sate',this.state.viewingElement);
}
see(name) {
this.setState({
viewingElement: {
name:name
}
});
console.log('State after SEE',this.state.viewingElement);
}
render() {
const { myData, viewingElement } = this.state;
return (
<div>
{myData.map(se => (
<SingleElement
key={se.id}
name={se.name}
see={this.see}
/>
))}
<SingleElementDetails viewingElement={viewingElement}/>
</div>
);
}
}
class SingleElement extends Component {
constructor(props) {
super(props);
}
seefunc(name) {
this.props.see(this.props.name);
console.log('Name in seefunc props',this.props.name);
}
render() {
return (
<div onClick={this.seefunc.bind(this)}>
DIV CONTENT
</div>
)
}
}
The problem you have here is that setState is asynchronous. It does work the first time but you do not see it in your console.log because the console.log happens before the state is updated.
To see the updated state use the second argument of setState which is a callback function (https://reactjs.org/docs/react-component.html#setstate):
this.setState({
viewingElement: {
name:name
}
}, () => {
console.log('State after SEE',this.state.viewingElement);
});
And in SingleElement use the componentWillReceiveProps(nextprops) (https://reactjs.org/docs/react-component.html#componentwillreceiveprops) method from react lifecycle to see the updated props:
seefunc(name) {
this.props.see(this.props.name);
}
componentWillReceiveProps(nextprops) {
console.log('Name in props',nextProps.name);
}
It does change. However setState is an aync process so you're only logging the previous state to the console. setState does provide a callback that allows you to run code after the async process has finished, so you can do:
this.setState({
viewingElement: {
name:name
}
}, () => console.log('State after SEE',this.state.viewingElement));
DEMO
Good day!
I keep getting
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.
which looks obvious, but i fail to see the loop in my component.
ComponentWillUpdate() shows that it calls lots of rerender with the same props and state in a short amount of time.
Thanks in advance.
src/TitleList.js
class TitleList extends Component {
constructor(props) {
super(props)
this.state = {'items': null}
}
onSortEnd = ({oldIndex, newIndex}) => {
this.setState({
items: arrayMove(this.state.items, oldIndex, newIndex),
});
};
render() {
if (this.props.allTitlesQuery && this.props.allTitlesQuery.loading){
return <div>Loading</div>
}
if (this.props.allTitlesQuery && this.props.allTitlesQuery.error) {
return <div>Error!</div>
}
const titlesToRender = this.props.allTitlesQuery.allTitles
this.setState({'items': titlesToRender})
return <SortableList
items={this.state.items}
onSortEnd={this.onSortEnd}
/>;
}
}
When you call this.setState it calls your renderagain. So if you call setState from render it goes to recursive loop.
You can try something as :-
class TitleList extends Component {
constructor(props) {
super(props)
this.state = {'items': null}
}
componentDidMount () {
this.updateState(props);
}
componentWillReceiveProps (nextProps) {
if (this.props.allTitlesQuery.allTitles !== nextProps.allTitlesQuery.allTitles) {
this.setState(nextProps);
}
}
updateState (props) {
this.setState({"items":props.allTitlesQuery.allTitles});
}
onSortEnd = ({oldIndex, newIndex}) => {
this.setState({
items: arrayMove(this.state.items, oldIndex, newIndex),
});
};
render() {
if (this.props.allTitlesQuery && this.props.allTitlesQuery.loading){
return <div>Loading</div>
}
if (this.props.allTitlesQuery && this.props.allTitlesQuery.error) {
return <div>Error!</div>
}
return <SortableList
items={this.state.items}
onSortEnd={this.onSortEnd}
/>;
}
}
Use componentDidMount method to render the data for first time and if data changes update using componentWillReceiveProps method
the loop is caused by this.setState({'items': titlesToRender}) in your render function
You shouldn't be calling setState inside of render, do it in another lifecycle method like componentDidMount or componentWillReceiveProps:
Render shouldn't modify state: https://reactjs.org/docs/react-component.html#render