setState not working in React JS - reactjs

I am trying to give few dates to state.periods array. But it is not working. My code is as follows.
class SmallTable extends Component {
constructor(props) {
super(props);
console.log(props.startDate)
this.state = {
turns: [],
periods: []
}
}
componentDidMount() {
//calculate years/ months and keep in one array
const today = new Date();
var periods1 = [];
if (this.props.period=="year") { //if year calculate from last year last date
const lastYearLastDate= new Date(new Date().getFullYear()-1, 11, 31)
periods1.push(lastYearLastDate.getFullYear()+"-"+(lastYearLastDate.getMonth()+1)+"-"+lastYearLastDate.getDate());
var lastYearFirstDate= new Date(lastYearLastDate.getFullYear()-1,0,1);
//for the remaining periods
for (var i=0;i<this.props.numberOfPeriods-1;i++) {
periods1.push(lastYearFirstDate.getFullYear()+"-"+(lastYearFirstDate.getMonth()+1)+"-"+lastYearFirstDate.getDate());
lastYearFirstDate = new Date(lastYearFirstDate.getFullYear()-1,0,1);
}
}
else if (this.props.period=="month") {//if month calculate from last month last date
var d=new Date(); // current date
d.setDate(1); // going to 1st of the month
d.setHours(-1); // going to last hour before this date even started.
var lastMonthLastDate = d;
periods1.push(lastMonthLastDate.getFullYear()+"-"+(lastMonthLastDate.getMonth()+1)+"-"+lastMonthLastDate.getDate());
//go to last month first date
var lastMonthFirstDate = new Date(lastMonthLastDate.getFullYear(), lastMonthLastDate.getMonth(),1);
//for the remaining periods
for (var i=0;i<this.props.numberOfPeriods-1;i++) {
periods1.push(lastMonthFirstDate.getFullYear()+"-"+(lastMonthFirstDate.getMonth()+1)+"-"+lastMonthFirstDate.getDate());
lastMonthFirstDate=new Date(lastMonthFirstDate.getFullYear(), lastMonthFirstDate.getMonth()-1,1);
}
}
console.log(periods1); -->prints ["2017-12-31", "2016-1-1", "2015-1-1", "2014-1-1"]
this.setState((prevState)=>{
return {
periods: prevState.periods.push(periods1)
}
});
console.log(this.state.periods) --> prints []
}
render() {
return ( <div></div>)
}
How to get values in periods1 to periods state. I am trying to insert periods1 array into state periods array. Those are strings. Pls suggest where the error might be.

You're setting this.state.periods to the result of a push operation. But push returns the new array length, not the array itself. Try this instead:
periods: [...prevState.periods, periods1]

push() doesn't return a value.
You should use:
this.setState((prevState)=>{
let old = prevState.periods.slice();
old.push(periods1);
return {
periods: old
}
});

You have a few issues.
For the code here:
return {
periods: prevState.periods.push(periods1)
}
You never want to mutate state. Instead, you should create a new array object and then add the data, like so:
return {
periods: prevState.periods.concat([periods1])
}
Secondly, your console.log is in the wrong place
console.log(this.state.periods) --> prints []
setState happens asynchronously and thus may not finish by the time your componentDidMount method returns. Instead, put that console.log inside your render function to see the new state.

If you expect this.state.periods to be an array of arrays ([["2017-12-31", "2016-1-1", "2015-1-1", "2014-1-1"]]) you can push your array following an immutable pattern using the spread operator :
this.setState((prevState) => ({
periods: [...prevState.periods, periods1]
}), () => { console.log(this.state.periods) } );
You can notice the function passed as second param of setState() is a callback to execute console.log() after the state update.
If you want to push periods1 values in this.state.periods you can do this :
this.setState((prevState) => ({
periods: [...prevState.periods, ...periods1]
}));

Try to make a copy of your original state, so that you can perform setState in an immutable fashion.
const periods = [...this.state.periods];
periods.push(periods1);
this.setState({periods: periods});

Related

Error: Too many re-renders , when modifying an array

Getting error when modifying array through large number of iterations.
data.logData1[0].data.map((values, index) => {
var result = {};
data.logData1[0].mnemonicList
.split(",")
.forEach((key, i) => (result[key] = values.split(",").map(Number)[i]));
setGraphData([...graphData, result]); //Modifying Array (here comes trouble)
});
Its difficult to say without code component, but I suspect that the problem lies in the fact that you are calling your state setter immediately inside the function component body, which forces React to re-invoke your function again, with the same props, which ends up calling the state setter again, which triggers React to call your function again.... and so on.
const resultData = data.logData1[0].data.map((values, index) => {
var result = {};
data.logData1[0].mnemonicList
.split(",")
.forEach((key, i) => (result[key] = values.split(",").map(Number)[i]));
return result
});
// somewhere in your useEffect or in function
setGraphData([...graphData, resultData]);
A work around can be you create a temporary variable and use it store the result from the loop and when are done looping, you can setGraphData to the final result
const tempVar = []
data.logData1[0].data.map((values, index) => {
var result = {};
data.logData1[0].mnemonicList
.split(",")
.forEach((key, i) => (result[key] = values.split(",").map(Number)[i]));
tempVar.push(result) //storing results to temporary array
});
setGraphData(tempVar); //setting the final result of the loop to graphData

React : Pushing result of map() to an array

Hello I am trying to map through an array of objects and push them to a new array.
My ISSUE : only the last item of the object is being pushed to the new array
I believe this has to do with React life cycle methods but I don't know where I should I loop and push the values to the array to get the full list
//My object in an array named states
var states = [{"_id":"Virginia","name":"Virginia","abbreviation":"VN","__v":0},{"_id":"North Carolina","name":"North Carolina","abbreviation":"NC","__v":0},{"_id":"California","name":"California","abbreviation":"CA","__v":0}];
export function StateSelect()
{
**EDIT 1**
const options = [];
function getStates()
{
//This is how I am looping through it and adding to an array
{ states.length > 0 &&
states.map(item =>
(
console.log(`ITEM: ${JSON.stringify(item)}`),
options.push([{ value: `${item.name}`, label: `${item.name}`}])
))
}
}
return( {getStates()}: );
}
Thank you
It looks like your getStates() might not even be returning anything... but assuming it is, I believe you should be able to accomplish this using a forEach() fn in order to push values into your options array... Try adding the following into your map:
states.map((item) => {
console.log(`ITEM: ${JSON.stringify(item)}`);
let processed = 0;
item.forEach((i) => {
options.push([{ value: `${i.name}`, label: `${i.name}`}]);
processed++;
if(processed === item.length) {
// callback fn, or return
}
}
.map usually used to return another result, you could just use .forEach
In fact, you don't really need to declare options at all, just use .map on state to return the result would be fine.
return states.length > 0 && states.map(({ name }) => {
return { value: name, label: name };
});

Can anyone explain why my state is getting updated even when i dont set it manually

So I just spent an hour debugging this code and finally got it to work, but I would want to know why this happened in the first place. I have a function that takes a value from my state, operates on it and saves the output in another variable in the state. This is the fuction:
getFolderNames = async () => {
const promises = this.state.rows.map(async item => {
if (item[".tag"] == "folder" && item.name.length > 20) {
item.name = await getFolderName(item.name);
return item;
} else return item;
});
const result = await Promise.all(promises);
this.setState({
rowsToDisplay: result
});
};
when i run this function, it was updating both the rows and rowsToDisplay to the result variable when i was only calling setState on only one of them.
Changing the function as below solves the issue but I would like to know why.
getFolderNames = async () => {
const promises = this.state.rows.map(async item => {
if (item[".tag"] == "folder" && item.name.length > 20) {
let item2 = {
...item
};
item2.name = await getFolderName(item.name);
return item2;
} else return item;
});
const result = await Promise.all(promises);
this.setState({
rowsToDisplay: result
});
};
It's because of how JavaScript handles variables. When you set a variable to an array or object, it doesn't make a new object but rather just references the original array/object.
As such, if you set a variable to equal some object, and then set a property of that variable, the original object will also be updated. Check this snippet for an example.
var foo = {changed: false};
var bar = foo;
bar.changed = true;
console.log("foo", foo.changed)
console.log("bar", bar.changed)
You can read more about the subject here: https://codeburst.io/explaining-value-vs-reference-in-javascript-647a975e12a0
I hope this helps you in the future, since I know I also spent many hours banging my head against exactly the sort of cases you described in your original question.

Setting nested array inside object in ReactJS

I have a Post like application, where a user can add comments with emojis to the post, which I have a method for:
addEmoji = (newEmoji) =>{
// mark if new emoji is already in the array or not
let containsNewEmoji = false;
let authors = []
authors.push(this.props.comment.author.name)
console.log(this.props.comment.author.name)
console.log(authors)
// recreate emojis array
let newEmojis = this.state.emojis.map(emoji => {
// if emoji already there, simply increment count
if (emoji.id === newEmoji.id) {
containsNewEmoji = true;
return {
...newEmoji,
...emoji,
count: emoji.count + 1,
authors: [...authors, authors]
};
}
// otherwise return a copy of previous emoji
return {
...emoji
};
});
console.log(authors)
// if newEmoji was not in the array previously, add it freshly
if (!containsNewEmoji) {
newEmojis = [...newEmojis, {...newEmoji, count: 1, authors: [...authors, authors]}];
}
// set new state
this.setState({ emojis: newEmojis,
showEmoji: true});
}
As shown in the method comments to the code, each emoji-only displays once, otherwise, a count variable will increment, to be shown below each comment.
I would like to add the feature, to save an array of the given username of the person, who added the emoji.
the username is given in as a prop
this.props.comment.author.name
so I have tried making an array to add the names 7
let authors = []
authors.push(this.props.comment.author.name)
the issue is that it's being overwritten each time a new emoji instance is being passed, I tried saving it to the object
return {
...newEmoji,
...emoji,
count: emoji.count + 1,
authors: [...authors, authors] // i want to save the old copy of authors and pass the new name
};
newEmojis = [...newEmojis, {...newEmoji, count: 1, authors: [...authors, authors]}]; // and then set the object in the end
As of now, the array is being overwritten each time, but could I set the parameter inside the object?
This is coming from setting the author field to an empty array early on in the code,
let authors = []
Instead it has to be set to the authors earlier on, as in:
authors: [..emoji.authors, author];
You should also consider using function of setState when dealing with setState.
addEmoji = (newEmoji) => {
const author = this.props.comment.author.name;
this.setState(({ emojis: prevEmojis }) => {
let containsNewEmoji = true;
const newEmojis = prevEmojis.map((emoji)=>{
if(newEmoji.id === emoji.id) {
containsNewEmoji = false;
return {
...emoji,
count: emoji.count + 1,
authors: [..emoji.authors, author];
}
} else {
return {
...emoji,
}
}
});
if(containsNewEmojis) {
newEmojis.push({
...newEmoji,
count: 1,
authors: [author],
});
}
return {
emojis: newEmojis,
}
});
}
I have reversed the containsNewEmoji variable so that it fits the context.
Yes, in the addEmoji method you're currently recreating the authors array each time addEmoji is called. Instead of defining a new authors array, push the new author into the existing authors property of the emoji.
Without knowing how the emoji object is initially created I can't give a definitive answer, but hopefully the following is a start. The solution assumes the emoji object has an authors property of type array.
addEmoji = (newEmoji) => {
// mark if new emoji is already in the array or not
let containsNewEmoji = false;
// recreate emojis array
let newEmojis = this.state.emojis.map(emoji => {
// if emoji already there, simply increment count
if (emoji.id === newEmoji.id) {
containsNewEmoji = true;
return {
...emoji,
count: emoji.count + 1,
authors: [...emoji.authors, this.props.comment.author.name]
};
}
// otherwise return a copy of the previous emoji
return emoji;
});
};

React - How to separately assign click event to bunch of div's generated in loop

I am trying to create my own Game of Life in React. Currently have created a map with divs that will be separate cells in future when I finish my project. I also wanted to attach click event to each cell, but for some reason when I click single cell, the entire set of cells is affected. Can you please check why this happens? Also, can you please let me know if my approach is correct? Here is my index.js code:
class Board extends React.Component {
constructor(props){
super(props);
this.state = {isToggleOn: true};
this.changeState = this.changeState.bind(this);
}
changeState() {
this.setState(prevState => ({
isToggleOn: !prevState.isToggleOn
}));
}
createMap = (cols, total) => {
let table = []; let nL = ''; let idRow = 0; let idCol = 0;
for (let i = 0; i < total; i++) {
idCol++;
if (i%cols === 0){
nL = 'newLine';
console.log(i%cols);
idRow += 1;
idCol = 0;
}
else {
nL = '';
}
let toggledBackground = (this.state.isToggleOn ? 'test' : '');
table.push(<div id={"row-"+idRow+"-"+idCol} className={nL+" square "+toggledBackground} onClick={this.changeState}></div>);
}
return table;
}
render() {
return(
<div>
{this.createMap(COLS, FIELDS)}
</div>
);
}
}
All of them are highlighted because they all share the same state, the easiest solution would be to make a separate component for the squares and pass down the needed data as props.
This will allow you to have separate state for every single cell.
I assume FIELDS is the total number of cells (e.g for a 10x10 board, that would make FIELDS = 100). If that's the case, then you could bind the current index for each iteration to the said cell you are pushing in.
This way, you'll know which cell was clicked.
onClick={() => this.changeState(i)}
You'll also need to add a parameter to the actual function declaration, and save the state for that particular cell:
changeState(index) {
this.setState(prevState => {
let arr = prevState.cellStates.slice();
arr[index] = !arr[index];
return {cellStates: arr};
});
}
Of course, this requires you to have an array, not a single boolean:
this.state = {cellStates: Array(FIELDS).fill(false)};
and finally for your styles:
let toggledBackground = (this.state.cellStates[i] ? 'test' : '');

Resources