setState method causes infinite loop of Results in Reactjs Functions - reactjs

The code displays users records from an array.
I also creates an updateCount() function which counts users on content display.
I can see the count results alerted and in the console.
Now I want to display the count results and to this effect, I initialize setState() method within the updateCount function.
updateCount = userId => {
...some code missing
this.setState({dataCount: count});
}
This causes infinite loop of the results due to re-rendering.
Is there anyway to get the setState out of the UpdateCount() function to enable me display the count results well or any better possible approach.
Here is the Code
import React, { Component, Fragment } from "react";
import { render } from "react-dom";
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [],
userCount: []
};
}
componentDidMount() {
var userId = "100";
this.setState({
data: [
{
id: "100",
name: "Luke"
},
{
id: "200",
name: "Henry"
},
{
id: "300",
name: "Mark"
}
]
});
this.updateCount = this.updateCount.bind(this);
}
updateCount = userId => {
var count = 1;
alert("counting: " + count);
console.log("counting: " + count);
this.setState({ dataCount: count });
};
render() {
return (
<span>
<label>
(---{this.state.dataCount}--)
<ul>
{this.state.data.map((person, i) => {
if (person.id == 100) {
//this.updateCount(person.id);
return (
<div key={i}>
<div>
{person.id}: {person.name}
</div>
</div>
);
} else {
this.updateCount(person.id);
}
})}
</ul>{" "}
</label>{" "}
</span>
);
}
}

You are calling in your render() method this.updateCount(person.id) which do a setState. Therefore a re-rendering occurs, and then this.updateCount(person.id) is called again. And it goes on.. (infinite loop)

From ReactJS 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.
Since updateCount() is calling setState(), you are calling setState in render. You need to redesign your code code, possibly creating array in state and using map in render.
Using getDerivedStateFromProps might be a good idea, make sure you to use a conditional statement, else you might hit another infinite loop.

Related

I wanna console.log the value after clicking the submit button once and to delete the previous mapped items, but it doesnt work

I'm very new to react and I got two problems:
I want to console log the input and display the mapped data after clicking the submit button once. But I get console logged the input and the mapped data after clicking the button twice.
I wanna clear the mapped list (data from previous input) and display new list items depending on the input. But the new list items are only added to the end of the previous list (only the last list item from the previous list got overwritten by the first list item of the new list).
So this is the code from my app component:
import React, { Component, Fragment } from 'react';
import './App.css';
import Display from './/Display';
class App extends Component {
constructor(props) {
super(props);
this.state = {
value: "",
passedValue: ""
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({ value: event.target.value });
}
handleSubmit(event) {
this.setState({ passedValue: this.state.value });
console.log(this.state.passedValue);
event.preventDefault();
}
render() {
return (
<div>
<form className="inputContainer" onSubmit={this.handleSubmit}>
<input type="text" name="company_name" onChange={this.handleChange} />
<input type="submit" value="Submit" />
</form>
<Display listDataFromParent={this.state.passedValue} />
</div>
);
}
}
export default App;
And this is my display component:
import React, { Component } from 'react'
import "./Display.css";
export default class Display extends Component {
constructor(props) {
super(props);
this.state = {
error: null,
isLoaded: false,
data: []
};
}
componentWillReceiveProps() {
fetch("http://localhost:5000/company?company_name=" + this.props.listDataFromParent)
.then(res => res.json())
.then(
(result) => {
this.setState({
isLoaded: true,
data: result
});
},
// Note: it's important to handle errors here
// instead of a catch() block so that we don't swallow
// exceptions from actual bugs in components.
(error) => {
this.setState({
isLoaded: true,
error
});
}
)
}
render() {
const { error, isLoaded, data } = this.state;
// if (error) {
// return <div>Error: {error.message}</div>;
// } else if (!isLoaded) {
// return <div>Loading...</div>;
// } else {
return (
<div className="display">
<h1>Kreditnehmer</h1>
<ul>
{this.props.listDataFromParent}
{data.map(item => (
<li key={item.c.company_id}>
Relation type: {item.r.relation_group}
Last name: {item.p.last_name}
</li>
))}
</ul>
</div>
);
}
}
Can anyone help?
1) setState is async method in react means it will take some time to update the component state. You can get your console log by using callback function of setState like
this.setstate({ value: e.target.value }, () => { console.log(this.state.value) });
2) in display component, your using componentWillReciveProps life cycle and inside that your using this.props.listdatafromparent which is pointing previous props. Rather than using this.props I would suggest consider props param of life cycle, means it should be like
componentWillReciveProps(props) {
// your code
Console.log(props.listdatafromparent);
}
The handleSubmit method is wrong... the console log is executed before the state is changed. You need to put it inside a callback function as a second parameter of setState.
this.setState({ passedValue: this.state.value }, () => {
console.log(this.state.passedValue);
});
Answers are:
1) Callback function should be used on setState, in order to do console.log after state is really updated.
In your case you call setState and setState is async function, which means that console.log won't wait until state is really updated.
Your code should be:
handleSubmit(event) {
this.setState({ passedValue: this.state.value },
() => console.log(this.state.passedValue));
event.preventDefault();
}
2) I would move data fetching out of componentWillReceiveProps(), since this lifecycle method will be deprecated from version 17 and it is fired on every render(). Try replacing with componentDidMount() or componentDidUpdate(). Maybe just this small change will solve your problem. If not pls post results and I will take a look again.

setState not updating the state object

I'm learning react from the book full stack react. In one of the examples votingapp where you have products and a button to vote for the product. That button supposes to increase the number of votes for that product and I store the votes number in a component Parent's state and display it in a child component. That voting feature is not working.
I created Parent component where it displays child component that present product description, id, color and votes (the number of votes the product received)
import React, { Component } from 'react';
import './App.css';
import Product from "./Product";
var products = [
{
id: 1,
name: "one",
color: "blue",
votes:0
},
{
id: 2,
name: "two",
color: "green",
votes : 0
},
{
id: 3,
name: "three",
color: "Red",
votes : 1
}
];
class App extends Component {
//this function will be passed to child component so the child can pass any data needed back to the parent using function argument.
handleProductUpVote(productId) {
console.log("Product #" + " " +productId + " was upvoted")
};
render() {
const productComponents = products.map((product) => {
return <Product
key={"product-" + product.id}
id={product.id}
name={product.name}
color={product.color}
votes={product.votes}
/*this is how we pass the function from parent to the child as a prop*/
onVote={this.handleProductUpVote}
/>
});
return (
<div className="App">
{productComponents}
</div>
);
}
}
export default App;
and here is my child component where it renders the product details
import React, { Component } from 'react';
class Product extends Component {
constructor(props) {
super(props);
this.handleUpVote = this.handleUpVote.bind(this);
}
//using the function passed from parent in a child. We pass any data needed back to parent component using parent's function arugments
// invoke this function using onClick event inside the button
handleUpVote() {
this.props.onVote(this.props.id);
};
render() {
return (
<div>
<p> name: {this.props.name} </p>
<p> MYcolor: {this.props.color} </p>
{/*invoke the function using onClick so it will invoke handleUpVote that will update the prop and pass the data back to parent*/}
<button onClick={this.handleUpVote}> Upvote Me </button>
<p>{this.props.votes}</p>
<hr></hr>
</div>
)
}
};
export default Product;
this is working and I log to the console the message when I hit the button "Upvoteme"
But When I'm trying to move the setup to use state. It doesn't work Here is the parent component with the state and setState. When I click on the vote button nothing happens to the vote count.
import React, { Component } from 'react';
import './App.css';
import Child from "./Child";
var productseed = [
{
id: 1,
name: "one",
color: "blue",
votes: 0
},
{
id: 2,
name: "two",
color: "green",
votes : 0
},
{
id: 3,
name: "three",
color: "Red",
votes : 1
}
];
class Parent extends Component {
constructor(props) {
super(props);
this.state = {
products: [],
};
this.handleProductUpVote = this.handleProductUpVote.bind(this);
}
componentDidMount() {
this.setState ({ products: productseed });
}
//this function will be passed to child component so the child can pass any data needed back to the parent using function argument.
handleProductUpVote(productId) {
// updating the vote in state
const nextProducts = this.state.products.map((product) => {
if (product.id === productId) {
return Object.assign({}, product, {
votes: product.votes + 1,
});
} else {
return product
}
});
this.setState({
products: nextProducts,
});
}
render() {
const productComponents = productseed.map((product) => {
return <Child
key={"product-" + product.id}
id={product.id}
name={product.name}
color={product.color}
votes={product.votes}
/*this is how we pass the function from parent to the child as a prop*/
onVote={this.handleProductUpVote}
/>
});
return (
<div className="App">
parent
{productComponents}
</div>
);
}
}
export default Parent;
The line below suppose to point to the products in the state but when I highlight it, it doesn't highlight the products in the this.state.
The consolelog is logging the message to my developer console. This is the issue, products in this.setState isn't pointing to the this.state.products and therefore not updating the state.
componentDidMount() {
this.setState({ products: productseed });
console.log("this products not pointing to the this.state.products, why?")
}
I read every question on stackoverflow related to setState not working and yet I have the same problem. If you an expert with react and able to take a look at this and figure out where is the issue, I would be so grateful. I'm unable to figure out when I assigned the this.setState({products: productseed}) and it doesn't update the state. I spent almost the past 4 hours reading and researching, please help.
Your problem lies in the render method of your Parent component. You're iterating over the productseeds array instead of your state. Since you're updating the state and not the seed array react sees no reason to rerender anything and therefore nothing changes.
So if you change that line from
const productComponents = productseed.map((product) => {...
to
const productComponents = this.state.products.map((product) => {...
you should be fine.
Moreover about your:
The line below suppose to point to the products in the state but when I highlight it, it doesn't highlight the products in the this.state.
This is just something related to the IDE you're using and nothing specific about react. You're passing an object with attributes and most IDEs (or all (?)) don't connect the combination with this.setState to the state object.

React Parent component checkbox state updates with one step delay

I have a Parent component:
import React, { Component } from "react";
import { Button } from "./Button";
export class Dashboard extends Component {
constructor(props) {
super(props);
this.state = {
numbers: [],
disabled: false
};
this.setNum = this.setNum.bind(this);
}
setNum(num) {
if (!this.state.numbers.includes(num)) {
this.setState(prevState => ({
numbers: [...prevState.numbers, num]
}));
} else if (this.state.numbers.includes(num)) {
let nums = [...this.state.numbers];
let index = nums.indexOf(num);
nums.splice(index, 1);
this.setState({ numbers: nums });
console.log(this.state.numbers);
}
if (this.state.numbers.length >= 4) {
this.setState({ disabled: true });
} else if (this.state.numbers.length < 4) {
this.setState({ disabled: false });
}
}
render() {
return (
<div className="board-container">
<div className="board">
<div className="row">
<Button
id="1"
numbers={this.state.numbers}
onChange={this.setNum}
disabled={this.state.disabled}
/>
<Button
id="2"
numbers={this.state.numbers}
onChange={this.setNum}
disabled={this.state.disabled}
/>
<Button
id="3"
numbers={this.state.numbers}
onChange={this.setNum}
disabled={this.state.disabled}
/>
<Button
id="4"
numbers={this.state.numbers}
onChange={this.setNum}
disabled={this.state.disabled}
/>
</div>
</div>
</div>
);
}
}
... and a Child component:
import React, { Component } from "react";
export class Button extends Component {
constructor(props) {
super(props);
this.state = {
isChecked: false
};
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
this.setState({
isChecked: !this.state.isChecked
});
var num = e.target.value;
this.props.onChange(num);
}
render() {
const { isChecked } = this.state;
if (isChecked === true) {
var bgColor = "#f2355b";
} else {
bgColor = "#f7f7f7";
}
let disabled = this.props.disabled;
if (this.props.numbers.includes(this.props.id)) {
disabled = false;
}
return (
<div className="number-container" id="checkboxes">
<label
className={!isChecked && disabled === false ? "num" : "checked-num"}
style={{ backgroundColor: bgColor }}
>
{" "}
{this.props.id}
<input
type="checkbox"
name={this.props.id}
value={this.props.id}
id={this.props.id}
onChange={this.handleChange}
checked={isChecked}
disabled={disabled}
/>
</label>
</div>
);
}
}
Whenever any Button component is clicked, the Parent component gets the child Button's id value and puts it into its numbers state array. Whenever a Button is unchecked, the Parent updates is numbers state by removing the id of the child Button.
If my code is right, the expected behavior is whenever a Button checkbox is clicked, the Parent numbers state will be updated immediately (adding or removing a number). However, it always updates with one step lag behind.
I know, that the issue is dealing with the React states not being updated instantly, and I've checked similar issues on Stackoverflow. The problem is that I can't figure it out how to make this two components interact with each other in a proper way. What would be the solution for this issue?
Here are three screenshots from codesandbox
If you want to play with it please find the link https://codesandbox.io/s/w2q8ypnxjw
What I did was, I basically copied and pasted your code and updated setNum function to reflect the changes Think-Twice suggested
setNum(num) {
if (!this.state.numbers.includes(num)) {
this.setState(
prevState => ({
numbers: [...prevState.numbers, num]
}),
() => {
console.log("state logged inside if", this.state.numbers);
}
);
} else if (this.state.numbers.includes(num)) {
let nums = [...this.state.numbers];
let index = nums.indexOf(num);
nums.splice(index, 1);
this.setState({ numbers: nums }, () => {
console.log("state logged inside else if", this.state.numbers);
});
}
if (this.state.numbers.length >= 4) {
this.setState({ disabled: true });
} else if (this.state.numbers.length < 4) {
this.setState({ disabled: false });
}
}
So before going further let's quickly address a couple of things regarding to React and setState
As B12Toaster mentioned and provided a link which contains a
quote from official documentation
setState() does not always immediately update the component. It may
batch or defer the update until later.
Think-Twice's also points out that by stating
Basically setState is asynchronous in React. When you modify a value
using setState you will be able to see the updated value only in
render..
So if you want to see the immediate state change in a place which
you trigger setState, you can make use of a call back function as
such setState(updater[, callback])
There are two approaches when it comes to and updater with setState,
you could either pass an object, or you could pass a function So in
Think-Twice's example, an object is passed as an updater
this.setState({ numbers: nums } //updater, () => {
console.log(this.state.numbers); //this will print the updated value here
});
When a function is used as an updater (in your setNum function you
already do that), the callback function can be utilized like below
if (!this.state.numbers.includes(num)) {
this.setState(
prevState => ({
numbers: [...prevState.numbers, num]
}),
() => {
console.log("state logged inside if", this.state.numbers);
}
);
}
Your current implementation and communication structure seems fine. It is actually called Lifting State Up which is recommended also by official documentation.
Basically you store the state of array numbers in a parent component (which can be considered as the source of truth) and you pass the method that changes the state as a prop to it's child component.
In the codesandbox link I provided, the functionalities works the way I expect (at least this is what I expect from your code)
Basically setState is asynchronous in React. When you modify a value using setState you will be able to see the updated value only in render. But to see updated state value immediately you need to do something like below
this.setState({ numbers: nums }, () => {
console.log(this.state.numbers); //this will print the updated value here
});

ComponentDidMount Updates State, but doesnt seem to re-render component

The problem I face is that it doesn't seem that componentDidMount is re-rendering my component, even though it is updating the state. Lot of code coming up, but it gives context to the issue I'm having. If I need to, I can upload screenshots of what is happening.
Here's the constructor:
export class Upload extends React.Component<RouteComponentProps<{}>, UploadTaggingOptions> {
constructor(props: any) {
super(props);
this.state = {
photographer: [
{ id: null, value: '', label: '' },
],
};
}
Here's component did mount:
componentDidMount {
//Fetch request for photographers from the db
fetch("http://localhost:49775/api/photographers")
.then(res => res.json())
.then((result) => {
var photographerData = this.state!.photographer;
var y = 0;
//Remove the empty object first and foremost. The list should now be totally empty
photographerData.shift();
//The loop to add the galleries to the galleryData array
for (var i in result) {
var id = result[i].id;
var value = result[i].firstname + ' ' + result[i].lastname;
var label = value;
var photographer = { "id": id, "value": value, "label": label };
photographerData.push(photographer);
y++;
}
this.setState({
isLoaded: true,
photographer: photographerData
});
},
(error) => {
this.setState({
isLoaded: true,
error
});
alert("Error loading options for the photographers. Refresh the page. If the error persists, please contact your administrator");
}
)
And finally Render:
public render() {
return <div>
<div className="photographers">
<p><b>Photographer:</b></p>
<DropDown options={this.state!.photographer} />
</div>
}
Just for clarity's sake, there are more components on the screen (hence the extra div for the dropdown component).
I'm not sure why, but the dropdown renders with the blank options I intitialize in the constructor, componentdidupdate does the fetch request AND updates the state to the data that was fetched, but I have to click the blank value in order to load those data values into the dropdown. It is almost like it re-renders after I change the selected value instead of on state change.
I've tried moving those requests into the constructor, but have the same problem. Perhaps
EDIT: Here's the code for the Dropdown component:
import * as React from 'react';
import Select from 'react-select';
const DropDown = (props: any) => {
return (
<div className="dropdown">
<Select
closeOnSelect={!(props.stayOpen)}
disabled={props.disabled}
options={props.options}
placeholder="Select an option..."
removeSelected={props.removeSelected}
simpleValue
value={props.selectedPhotographer}
searchable={true}
multi={true}
/>
</div>
)
}
export default DropDown;
From react official documentation:
Never mutate this.state directly, as calling setState() afterwards may replace the mutation you made. Treat this.state as if it were immutable.
But in your code you are mutating it, albeit via an assignment to another variable:
var photographerData = this.state!.photographer;
// this DOES mutate the original array.
photographerData.shift();
This can mess with Reacts batching update strategy and can cause delays.
If you do not need the data from original state, you can just do:
var photographerData = [];
window.onload = function() {
console.log('Testing');
let testArr1 = [1, 2, 3]
console.log('TestArr1 length before shift: ' + testArr1.length);
let testArr2 = testArr1;
testArr2.shift();
console.log('TestArr1 length after shift: ' + testArr1.length);
}

Incremental React Game with setInterval: Cannot update during existing state transition

Im trying to get this code working as a "incremental" game, where the options will be displayed after some credits, but Im getting the following warning (only once):
Warning: Can't call setState (or forceUpdate) on an unmounted component.
index.js:2178 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.
Here is the code:
import React, { Component } from 'react';
import { Grid, Button } from "semantic-ui-react";
class EventDashboard extends Component {
constructor(props) {
super(props)
this.state={
credits: 0,
newOption: false,
newOptionDirty: false,
}
this.addCredits = this.addCredits.bind(this)
this.renderNewOption = this.renderNewOption.bind(this)
this.intervalID = null;
}
addCredits() {
this.setState((previousState) => ({
credits: previousState.credits + 1
}))
}
componentDidMount() {
this.intervalID = setInterval(() => {
this.addCredits()
}, 1000)
}
componentWillUnmount() {
clearInterval(this.intervalID);
}
renderNewOption() {
if(this.state.credits === 5 && !this.state.newOptionDirty) {
// the warning happens here
this.setState(() =>({
newOptionDirty: true
}))
}
if(this.state.newOptionDirty) {
return(
<div>
<Button> Add new feature </Button>
</div>
)
} else {
return (
<div>no options until 5 credits</div>
)
}
}
render() {
return (
<Grid>
<Grid.Column width={10}>
<Button
onClick={this.addCredits}
>AddCredits</Button>
{this.renderNewOption()}
</Grid.Column>
<Grid.Column width={6}>
<h1>Credits</h1>
<h2>{this.state.credits}</h2>
</Grid.Column>
</Grid>
)
}
}
export default EventDashboard
In spite of this warning everything is working fine.
What good practice Im missing?
The problem is indeed with your renderNewOption() method and how you use it.
You are invoking renderNewOption() from render(), which is bad because you are doing a setState() in it. Remember that whenever you do setState() you update the state and trigger a new render. As such, having setState() inside the render() would create an infinite loop since the render function would keep calling itself.
In this particular case, you won't actually get an infinite loop because you have an if-case that prevents that, however your component will run it the first time (when newOptionDirty is still false). When that happens, your component hasn't mounted yet because the render() never finished before the this particular setState() was invoked.
TL;DR: Never call setState() during a render.
Try this instead:
componentDidMount() {
setInterval(() => {
this.addCredits()
}, 1000);
}
addCredits() {
this.setState((previousState) => ({
credits: previousState.credits + 1
});
}
renderNewOption() {
if(this.state.credits >= 5) {
return(
<div>
<Button> Add new feature </Button>
</div>
)
} else {
return (
<div>no options until 5 credits</div>
)
}
}
We don't need (and shouldn't) to create some new state variable inside the render. All we are interested in is if credits are equal to 5 or not.
Depending on its value, we return the appropriate React Element (button or no button).
Whenever you are doing any asynchronous stuff in your component like callbacks/Promises/setTimeout you might run into a situation when a component might have already been unmounted and you will try to update umounted component ( like in your case ) which might lead to memory leaks. This is one of the use cases for external state management libraries and middlewares like redux and redux-saga/redux-observable.
If you are going to do it in your components however you need to take care to do necessary cleanups when component unmounts. This is what for componentWillUnmount is used for:
constructor(props) {
super(props)
...
this.intervalID = null;
}
componentDidMount() {
this.intervalID = setInterval(() => {
this.addCredits()
}, 1000)
}
componentWillUnmount() {
clearInterval(this.intervalID);
}

Resources