Too much recursion when updating state in react - javascript

In this example, when I try to update the state during the componentDidUpdate life cycle callback, I get a too much recursion error. How should I be updating the state?
import React from 'react';
class NotesContainer extends React.Component {
constructor(props) {
super(props);
this.state = { listOfShoppingItems: [] };
}
componentDidUpdate(nextProps, nextState) {
let newShoppingItems = this.calculateShoppingItems();
this.setState({ listOfShoppingItems: newShoppingItems });
}
calculateShoppingItems() {
let shoppingItemsCart = []
if (this.props.milk < 3) {
let value = "Buy some milk";
shoppingItemsCart.push(value);
}
if (this.props.bread < 2) {
let value = "Buy some bread";
shoppingItemsCart.push(value);
}
if (this.props.fruit < 10) {
let value = "Buy some fruit";
shoppingItemsCart.push(value);
}
if (this.props.juice < 2) {
let value = "Buy some juice";
shoppingItemsCart.push(value);
}
if (this.props.sweets < 5) {
let value = "Buy some sweets";
shoppingItemsCart.push(value);
}
return shoppingItemsCart;
}
render() {
return (
<div>
Etc...
</div>
);
}
}
export default NotesContainer;

componentDidUpdate is triggered when either the props or the state has changed. If you change the state in this method, you are causing an infinite loop (unless you implement shouldComponentUpdate).
It looks like your state changes when you receive new props, therefore componentWillReceiveProps seems a good place. From the docs:
Invoked when a component is receiving new props. This method is not called for the initial render.
Use this as an opportunity to react to a prop transition before render() is called by updating the state using this.setState(). The old props can be accessed via this.props. Calling this.setState() within this function will not trigger an additional render.

Related

Understanding immutability and virtual DOM in below case

After reading here about immutability, I am trying to understand how react is working. I am trying to understand with the help of following 3 components, however its not making sense.
const bla = {a: 1};
class Test extends React.Component {
constructor(props) {
super(props);
this.state = {
a: 1,
};
this.onClick = () => this.setState(prevState => ({
a: prevState.a + 1
}));
}
render() {
console.log('Render default');
return <div id="test" onClick = {
this.onClick
} > DOM Updating: {
this.state.a
} < /div>;
}
}
class Test1 extends React.Component {
constructor(props) {
super(props);
this.state = {
a: 1,
};
this.onClick = () => this.setState(prevState => ({
a: 1
}));
}
render() {
console.log('Render 1');
return <div id="test1" onClick = {
this.onClick
} > DOM not updating: {
this.state.a
} < /div>;
}
}
class Test2 extends React.Component {
constructor(props) {
super(props);
this.state = bla;
this.onClick = () => {
const mutating = this.state;
mutating.a = this.state.a + 1;
this.setState(mutating);
};
}
render() {
console.log('Render 2');
return <div id="test2" onClick = {
this.onClick
} > DOM updating with mutation: {
this.state.a
} < /div>;
}
}
ReactDOM.render( < div > < Test / > < Test1 / > < Test2 / > < /div>, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
If I inspect div id="test1" it is updating in Chrome dev tool on each click. Want to understand below points:
During on click If I inspect div id="text1" why this is not reflecting in dev tool? Here I am assigning new object in state on each click ({a: 1}). As the link above says
In fact, React.js does not need to have knowledge about what exactly changed. All it needs to know is whether the state changed at all or not.
In Test2 component I am mutating the state however, why dom getting updated on click?
In case of updating nested state since we are updating parent state, does this mean React will think that all others children have changed their values and will re-render all of them even when they aren't changed?
1.) In the console output, you simply see Render 1, because you invoke setState with the static value 1. But in fact, each time you invoke setState with the click, React will set a new (in the sence of new object reference) state for the component, and a new render cycle is triggered.
setState() will always lead to a re-render unless shouldComponentUpdate() returns false. Link
2.) See answer 1. In addition, you have a DOM change, because state changes are merged.
When you call setState(), React merges the object you provide into the current state.
In your Test2 click handler code, mutating will be merged into the current state. It is the same object, but important is, that all its properties (here a property) will be spread into the state, resulting in a new state object.
const mutating = this.state;
mutating.a = this.state.a + 1;
this.setState(mutating);
3.) Let's say you have a Parent component containing all your child components. If you change props or state of Parent, all children will be rerendered by React, unless their shouldComponentUpdate hook returns false or they are PureComponents, whose props or state did not change.
setState() enqueues changes to the component state and tells React that this component and its children need to be re-rendered with the updated state. Link
Rerender means, that for each child, React compares the previous and the current version in a Reconciliation step. It only leads to a DOM update, if the child has really changed, otherwise nothing happens. See here for a nice visualization.

How to not use setState inside render function in React

I have a complete running code, but it have a flaw. It is calling setState() from inside a render().
So, react throws the anti-pattern warning.
Cannot update during an existing state transition (such as within render or another component's constructor). Render methods should be a pure function of props and state; constructor side-effects are an anti-pattern, but can be moved to componentWillMount
My logic is like this. In index.js parent component, i have code as below. The constructor() calls the graphs() with initial value, to display a graph. The user also have a form to specify the new value and submit the form. It runs the graphs() again with the new value and re-renders the graph.
import React, { Component } from 'react';
import FormComponent from './FormComponent';
import PieGraph from './PieGraph';
const initialval = '8998998998';
class Dist extends Component {
constructor() {
this.state = {
checkData: true,
theData: ''
};
this.graphs(initialval);
}
componentWillReceiveProps(nextProps) {
if (this.props.cost !== nextProps.cost) {
this.setState({
checkData: true
});
}
}
graphs(val) {
//Calls a redux action creator and goes through the redux process
this.props.init(val);
}
render() {
if (this.props.cost.length && this.state.checkData) {
const tmp = this.props.cost;
//some calculations
....
....
this.setState({
theData: tmp,
checkData: false
});
}
return (
<div>
<FormComponent onGpChange={recData => this.graphs(recData)} />
<PieGraph theData={this.state.theData} />
</div>
);
}
}
The FormComponent is an ordinary form with input field and a submit button like below. It sends the callback function to the Parent component, which triggers the graphs() and also componentWillReceiveProps.
handleFormSubmit = (e) => {
this.props.onGpChange(this.state.value);
e.preventdefaults();
}
The code is all working fine. Is there a better way to do it ? Without doing setState in render() ?
Never do setState in render. The reason you are not supposed to do that because for every setState your component will re render so doing setState in render will lead to infinite loop, which is not recommended.
checkData boolean variable is not needed. You can directly compare previous cost and current cost in componentWillReceiveProps, if they are not equal then assign cost to theData using setState. Refer below updated solution.
Also start using shouldComponentUpdate menthod in all statefull components to avoid unnecessary re-renderings. This is one best pratice and recommended method in every statefull component.
import React, { Component } from 'react';
import FormComponent from './FormComponent';
import PieGraph from './PieGraph';
const initialval = '8998998998';
class Dist extends Component {
constructor() {
this.state = {
theData: ''
};
this.graphs(initialval);
}
componentWillReceiveProps(nextProps) {
if (this.props.cost != nextProps.cost) {
this.setState({
theData: this.props.cost
});
}
}
shouldComponentUpdate(nextProps, nextState){
if(nextProps.cost !== this.props.cost){
return true;
}
return false;
}
graphs(val) {
//Calls a redux action creator and goes through the redux process
this.props.init(val);
}
render() {
return (
<div>
<FormComponent onGpChange={recData => this.graphs(recData)} />
{this.state.theData !== "" && <PieGraph theData={this.state.theData} />}
</div>
);
}
}
PS:- The above solution is for version React v15.
You should not use componentWillReceiveProps because in most recent versions it's UNSAFE and it won't work well with async rendering coming for React.
There are other ways!
static getDerivedStateFromProps(props, state)
getDerivedStateFromProps is invoked right before calling the render
method, both on the initial mount and on subsequent updates. It should
return an object to update the state, or null to update nothing.
So in your case
...component code
static getDerivedStateFromProps(props,state) {
if (this.props.cost == nextProps.cost) {
// null means no update to state
return null;
}
// return object to update the state
return { theData: this.props.cost };
}
... rest of code
You can also use memoization but in your case it's up to you to decide.
The link has one example where you can achieve the same result with memoization and getDerivedStateFromProps
For example updating a list (searching) after a prop changed
You could go from this:
static getDerivedStateFromProps(props, state) {
// Re-run the filter whenever the list array or filter text change.
// Note we need to store prevPropsList and prevFilterText to detect changes.
if (
props.list !== state.prevPropsList ||
state.prevFilterText !== state.filterText
) {
return {
prevPropsList: props.list,
prevFilterText: state.filterText,
filteredList: props.list.filter(item => item.text.includes(state.filterText))
};
}
return null;
}
to this:
import memoize from "memoize-one";
class Example extends Component {
// State only needs to hold the current filter text value:
state = { filterText: "" };
// Re-run the filter whenever the list array or filter text changes:
filter = memoize(
(list, filterText) => list.filter(item => item.text.includes(filterText))
);
handleChange = event => {
this.setState({ filterText: event.target.value });
};
render() {
// Calculate the latest filtered list. If these arguments haven't changed
// since the last render, `memoize-one` will reuse the last return value.
const filteredList = this.filter(this.props.list, this.state.filterText);
return (
<Fragment>
<input onChange={this.handleChange} value={this.state.filterText} />
<ul>{filteredList.map(item => <li key={item.id}>{item.text}</li>)}</ul>
</Fragment>
);
}
}

ReactJS element not updating after setState being called

Good Afternoon,
I have a React component that is dynamically rendered in reponse to an API call. I have set the value of one of the elements to a state within the component. During an onClick function (minusOne) this value is supposed to change.
The value is initially rendered successfully based on the state, the function does indeed change the state, however the rendered element stays the same despite the state changing. Does anyone have any ideas of why this might be the case?
If you have any questions, please ask away!
export class Cart extends React.Component {
constructor(props) {
super(props);
this.state={
quantities: []
};
this.minusOne = this.minusOne.bind(this);
}
minusOne(i) {
var self = this;
return function() {
let quantities = self.state.quantities;
if (quantities[i] > 1) {
quantities[i] --;
}
self.setState({
quantities
})
}
}
componentDidMount() {
let cart = this.props.cartTotals;
this.setState({
cart
});
if(cart.lines) {
let cartTotal = [];
let quantities = [];
for (var i = 0; i < cart.lines.length; i++) {
if(cart.lines[i]) {
quantities.push(cart.lines[i].quantity);
}
}
//Initial setting of state
this.setState({
quantities
})
Promise.all(
cart.lines.map(
(cart, i) => axios.get('http://removed.net/article/' + cart.sku)
)
).then(res => {
const allCartItems = res.map((res, i) => {
const data = res.data;
return(
<div key={i} className="cart-item-container">
<img className ="cart-item-picture" src={data.image} name={data.name} />
<div className="cart-item-description">
<p>{data.name}</p>
<p>{data.price.amount} {data.price.currency}</p>
</div>
<div className="cart-item-quantity">
<button onClick={this.minusOne(i)} name="minus">-</button>
//This is the troublesome element
<p className="cart-current-quantity">{this.state.quantities[i]}</p>
<button name="plus">+</button>
</div>
</div>
)
})
this.setState({
allCartItems
})
})
}
}
render() {
return (
{this.state.allCartItems}
);
}
}
Thanks for reading! Any advice will be helpful.
There are two issues:
First, you need to render (including where the onClick is) in render(). ConponentDidMount is only called once and supposed to perform initialization but not render.
Then, there is a problem in minusOne:
quantities points to this.state.quantities. So you are changing the old state, React looks at both the old state and the new one, sees there is no change, and dodesn't render, although the values have changed.
If you will copy this.state.quantities to a new array, like:
newQ = this.state.quantities.slice(0, -1);
Then modify newQ, then do
this.setState({ quantities: newQ });
It should work.
I think you don't need to return a function at minusOne(i) method. Just update the state is enough. You should change the array by specific id.
let quantities = self.state.quantities;
let mutatedQuantities = quantities.map((el, index) => {
return (index === i) ? el - 1 : el;
})
this.setState({quantities: [...mutatedQuantities]})
--- edited ---
I deleted everything I wrote before to make it more concise.
Your problem is that you assign what you want to render to a variable in componentDidMount. This function does only get called once, hence you asigne the variable allCartItems only once. The setState function does not have any effect because it does not trigger componentDidMount and therefore your variable allCartItems does not get reassigned.
What can you do? Well you can do a lot of stuff to enhance your code. First I will let you know about how you can solve your problem and then give you some further improvements
To solve the problem of your component not updating when you call setState you should move your jsx to the render Method. In the componentDidMount you just get all the data you need to render your component and once you have it you can set a flag for example like ready to true. Below you can see an example of how your code could look like.
import React from 'react';
class Cart extends React.Component {
constructor(props) {
super(props);
this.state = {
carts: null,
ready: false,
};
}
componentDidMount() {
fetch('www.google.com').then((carts) => {
this.setState({
carts,
ready: true,
});
});
}
render() {
const myCarts = <h2> Count {this.state.carts} </h2>;
return (
{
this.state.ready
? myCarts
: <h2> Loading... </h2>
}
);
}
}
I made you a demo with a simple counter with some explanations of your case and how you can make it work. You can check it out codesandbox. In the NotWorkingCounter you can see the same problem as in your component of the variable not being updated. In the WorkingCount you can see an example where I implemented what a I wrote above with waiting until your data has arrived and only then render it.
Some more suggestions concerning code:
Those two syntaxes below are identical. One is just a lot more concise.
class Cart extends React.Component {
constructor(props) {
super(props);
this.state = {
carts: null,
ready: false,
};
}
}
class Cart extends React.Component {
state = {
carts: null,
ready: false,
}
}
I would suggest to use arrow function if you want to bind your context. Below you can see your example simplified and an example on how you can achieve the same thing with less syntax.
export class Cart extends React.Component {
constructor(props) {
super(props);
this.minusOne = this.minusOne.bind(this);
}
minusOne(i) {
///
}
}
export class Cart extends React.Component {
constructor(props) {
super(props);
}
minusOne = (i) => {
/// minus one function
}
}
Your minusOne could also be rewritten if you use arrow functions and be a lot smaller, something in the area of
minusOne = (i) => (i) => {
let quant = self.state.quantities[i];
if(quant > 1) {
this.setState({
quantities: quant-1,
})
}
}
In your componentDidMount you call this.setState twice. Every time you call this function your component gets rerender. So what happens in your component is when your mount your component it gets rendered the first time, once it is mounted componentDidMount gets called, in there you call this.setState again twice. This means your component get's rendered in the best case three times before the user sees your component. If you get multiple promises back this means your rerender your state even more. This can create a lot of load for your component to cope with. If you rerender every component three times or more you end up having some performance issues once your application grows. Try to not call setState in your componentDidUpdate more than once.
In your case your first call to setState is totally unnecessary and just creates load. You still have access to quantities in your promise. Just call setState once at the end of your promise.then() with both elements.
In the example below you are using the index i as a key. This is not a good case practice and react should also log you at least a warning in the console. You need to use a unique identifier which is not the index. If you use the index you can get sideeffects and weird rendering which is difficult to debut. Read more on it here
then(res => {
const allCartItems = res.map((res, i) => {
const data = res.data;
return(
<div key={i} className="cart-item-container">
Another suggestion is to replace all var with const or let, as var exposes your variable to the global scope. If you don't understand what that means read this.
Last but not least have a look at object deconstruction. It can help you to clean up your code and make it more resistant to unwanted sideffects.

Handling onchange in two stateful components in React JS

I am new to react and so confused in handling and calling the onChange events.
Now , I have 2 components :-
1. Parent component -
updateField = e => {
console.log("update field e called");
this.setState({
value: e.target.value
});
};
<InputTypeahead value={this.state.value} label="Email" onChange={this.updateField} typeaheadItems={this.emailAdressess} /
where I am calling the onChange and taking the current value out. Till
now whatever I type in Input I get the value.
Now,
2.In Child component :
I want to take the value coming from this parent component and using that would like to setstate.
How to achieve this in React js ? I have tried using refs , but result was not successful.
Any Help is appreciated.Thanks.
From version i.e 16.3.0 onwards, you can make use of getDerivedStateFromProps method to update the state based on props like
class InputTypeahead extends React.Component {
state = {
value: ''
}
static getDerivedStateFromProps(nextProps, prevState) {
if(nextProps.value !== prevState.value) {
return { value: nextProps.value};
}
return null;
}
}
According to 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.
Before v16.3.0, you would make use of constructor along with componentWillReceiveProps like
class InputTypeahead extends React.Component {
constructor(props) {
super(props);
this.state = {
value: props.value
}
}
componentWillReceiveProps(nextProps) {
if(nextProps.value !== this.props.value) {
this.setState({ value: nextProps.value});
}
}
}

React - method run right times via state but run double times when Parent Component changing state

I am trying to build a page with some data initialized at first time mounted, and update when websocket server give a response msg when certain button click event is triggered, also I need to ban the button aka. disabled, and tell the user in how many seconds the button is clickable again.
My first thought is, single component, update via states, give a state to the counter, then use setTimeout to count down 1 every 1000ms, turned out that the counter "banCount" worked well, until I add the websocket.send(), then it counted down 2 every time.
I thought that would be because when the websocket server responsed, the state is change, so the whole component is updated, the counter is messed up.
So, I had an idea, separating it into a child component, with its own state, but do nothing when in the life cycle of componentWillReceiveProps, and it will not receive props, so it will just work with it is own state. But the result is with or without separating the counter into a child component, they worked the same.
parent component:
import React from 'react';
import ReactDOM from 'react-dom';
import TestChild from './testChild/testChild';
class TestParent extends React.Component {
constructor(props) {
super(props);
this.state = {
wsData: null,
};
}
componentWillMount() {
this.wsClient = new WebSocket("ws://localhost:9000/server", 'echo-protocol');
this.wsClient.onmessage = msg => {
if (msg) {
this.setState({
wsData: msg.data
});
}
};
}
render() {
const data = () => {
if (this.state.wsData) {
return this.state.wsData;
} else {
return "waiting data";
}
};
return (
<div>
<div>{data()}</div>
<TestChild wsClient={this.wsClient}/>
</div>
);
}
}
ReactDOM.render(
<TestParent />,
document.getElementById('reactWrapper')
);
and the Child Component:
import React from 'react';
class TestChild extends React.Component {
constructor(props) {
super(props);
this.count = null;
this.state = {
banCount: this.count
};
this.wsClient = this.props.wsClient;
this.countupdate = 0;
}
banCount() {
this.setState({
banCount: this.count
});
}
callNext(n) {
this.wsClient.send('can you hear me');
this.count = n;
this.banCount();
}
componentDidUpdate() {
if (this.count > 0) {
setTimeout(() => {
this.count -= 1;
this.banCount();
}, 1000);
} else if (this.count === 0) {
this.count = null;
this.banCount();
}
}
render() {
return <button onClick={() => this.callNext(3)}>click me {this.state.banCount}</button>;
}
}
export default TestChild;
Please ignore 'whether the server and websocket connection' works part, they are fine.
I don't know why, I even had not updated Child component, I am really new to React, I really do not know how to debug this, I read this code for hours, but it is just too complicated for me.
Why it counted down 2 every time? and for sure I am wrong, what is the right way.
Please help me with only React and vanilla Javascript, I had not use Redux or Flux and even did not know what they are, thank you.
This is NOT tested code, but should help you to build what you want, I didn't tested your component but I suspect that your setTimeout() is called several times.
import React from 'react';
class TestChild extends React.Component {
constructor(props) {
super(props);
this.state = {
count: null,
};
}
startCountDown() {
var newCount = this.state.count -1;
if(newCount === 0){
clearTimeout(this.timer);
}
this.setState({
count: newCount,
});
}
callNext(n) {
this.wsClient.send('can you hear me');
this.setState({
count: n,
});
this.timer = setTimeout(() => {
startCountDown();
}, 1000);
}
componentWillUnmount() {
clearTimeout(this.timer);
}
render() {
return <button disabled={this.state.count>0} onClick={() =>
this.callNext(3)}>click me {this.state.count}</button>;
}
}
export default TestChild;
Finally I worked it out.
It is because React will re-render all the child component with or without setting children's new states. The only way to stop it from re-render is to use ShouldComponentUpdate, so:
shouldComponentUpdate() {
return this.state.banCount !== null;
}
will work, as when the child component receiving props after websocket.send(), this.count is still null, but right after the websocket.send(), this.count is set to 3, so the child component will update since.
Also another workround:
callNext(n) {
this.wsClient.send('can you hear me');
this.count = n;
}
componentWillReceiveProps(nextProps) {
this.data = nextProps.datas;
this.setState({
banCount: this.count
});
}
in this workround, without shouldComponentUpdate() the child component will always re-render when its parent receive websocket data, so in the click handler function, stop calling bancount(), so it would not update itself, but set the state when receive nextProps, that will trigger the re-render.
To sum all above:
child component will always re-render with or without setting state via new props unless shouldComponentUpdate return false, I alreay called bancount() in the click handler function, trigger child component to update the state itself, but after the parent component receiving websocket data, it triggered state updating again, that is why it run double times.

Categories