Why is concat on array inside this.state not working? - reactjs

I'm trying to add rendered dates inside of my array located in my component state under the key date, but it always returns 0.
constructor(props) {
super(props);
this.state = {
dates: []
}
}
componentWillMount() {
this.renderDateComp()
}
renderDateComp() {
for(var i = 0; i < 5; i++) {
var dat = new Date(Date().valueOf());
dat.setDate(dat.getDate() + i);
this.setState({ dates: this.state.dates.concat(dat) });
console.log(this.state.dates); //prints length of 0
}
}

Well firstly, state transitions aren't immediate, they are asynchronous (in most circumstances). Think of it like every time you set the state, it adds that state change to a queue of changes and React is continuously working through that queue, doing all those state changes. So if you change the state, then on the next line of code immediately print the state, the state probably won't have changed because the console.log happens IMMEDIATELY after the state change is added to the list, but BEFORE the change has actually gone through that queue.
Secondly, it's probably better to set the state AFTER the for loop, so you only have to set it once, not 5 times.

The recommended way to do it would be to:
Call setState only once after you have the final dates array.
Log this.state.dates in the callback of setState (it will only be called after this.state has been properly updated by React.
The key point to note here is that setState is asynchronous. You probably do not need to console.log the array in your real code.
Read more about setState here.
constructor(props) {
super(props);
this.state = {
dates: []
}
}
componentWillMount() {
this.renderDateComp()
}
renderDateComp() {
var dates = this.state.dates;
for (var i = 0; i < 5; i++) {
var dat = new Date(Date().valueOf());
dat.setDate(dat.getDate() + i);
dates = dates.concat(dat);
}
this.setState({
dates
}, () => {
console.log(this.state.dates); //prints length of 5
});
}

It's worth reading the documentation of setState(). https://facebook.github.io/react/docs/react-component.html#setstate
It will queue the change, but there is a callback version if you want to use the new value somehow.
this.setState((prevState, props) => {
return {myInteger: prevState.myInteger + props.step};
});

More specifically, if you do this:
this.setState({ dates: this.state.dates.concat(dat) }, () => console.log(this.state.dates));
You'll get the result you're expecting.

Related

setState accept function vs object

Lets say I have a state that I would like to update:
state = {
description: "",
notes: ""
}
Is there a difference if I update the state with just an object, if I do not need to use prevState?
this.setState({description: "Blue"});
vs
this.setState((prevState)=> ({description: "Blue"}));
I've created the demo to visualize the difference
/* When we doing this state gets update after the for loops runs so,
this.state.count is same for each repeat so even if we expecting to
count increase by 5 its actually increment by 1 */
updateCountByFive = () => {
for (let a = 0; a < 5; a++) {
this.setState({
count: this.state.count + 1
});
}
};
/* In this case by using callback to set the new state,
react is smart enough to pass the latest count value to the callback
event if that new change not rendered */
updateCountByFiveCallback = () => {
for (let a = 0; a < 5; a++) {
this.setState(prevState => {
return {
count: prevState.count + 1
};
});
}
};
So it's a good practice to use callback version when you need to use current state to set the next state, since it will prevent some issues as above
This is what react doc says.
React may batch multiple setState() calls into a single update for performance.
Because this.props and this.state may be updated asynchronously, you should not rely on their values for calculating the next state.
there is a nice article here

How can i fill a array on state?

Im using onError method to call this function:
And I need to set on state every time when onError method is executed.
brokenMobImg(e){
var image = e.target
image.parentNode.removeChild(image);
this.setState({broken: e.target.src})
console.log('IMGS:', this.state.broken);
}
You need to setup your errors as an array in state first then update the array with your new error rather than just replacing your broken variable each time. Here is a codesandbox example that does something similar to what you are wanting to do: https://codesandbox.io/s/currying-haze-cshem?fontsize=14
Current:
brokenMobImg(e){
var image = e.target
image.parentNode.removeChild(image);
this.setState({broken: e.target.src})
console.log('IMGS:', this.state.broken);
}
Solution:
constructor(props){
super(props);
this.state ={
broken:[]
}
}
brokenMobImg(e){
var image = e.target;
var brokenInState = this.state.broken;
image.parentNode.removeChild(image);
brokenInState.push(e.target.src);
this.setState({broken: brokenInState},()=>{
console.log('IMGS:', this.state.broken);
});
}

React - how to get array from store before first render and use it as dataset for ChartJS

I've implemented my own method to handle click event at chart label (React ChartJS 2 library) and it's for filtering data. I have a little problem becouse I'm storing state of filters for specific user so at first, when I'm initializing view with that chart I have to get array with actual filters. The problem is that even when I'm getting this array, I can't use it in my method becouse it's always undefined. What is important - every time I'm adding new filter, i'm also sending array of filters to store. I'm pretty new in React so Your advices will be really welcome.
This is part of my code (mayby componentDidMount is not good choice?):
constructor(props) {
super(props);
this.state = {
itemsArray: []
};
}
componentDidMount(): void {
// Overview doughnut chart filtering
AppStore.instance.select(selectQuickQueryParams).pipe(takeUntil(this.componentDestroyed$)).subscribe((quickQueryParam: QueryParamListState) => {
this.setState({itemsArray: quickQueryParam.entities['Connection:NOT IN:connectionType:QuickFiltersComponent'] ? quickQueryParam.entities['Connection:NOT IN:connectionType:QuickFiltersComponent'].value : []})
});
const originalDoughnutLegendBehavior = Chart.defaults.doughnut.legend.onClick;
Chart.defaults.doughnut.legend.onClick = function (e, legendItem) {
if (!legendItem.hidden) {
if (!this.state.itemsArray.includes(legendItem.text)) {
this.state.itemsArray.push(legendItem.text);
}
const query = new QueryParam(EQueryEntity.Connection, 'connectionType', this.itemsArray, EQueryOperator.NotIn, 'QuickFiltersComponent');
AppStore.instance.dispatch(new AddQuickQueryParamsAction([query]));
AppStore.instance.dispatch(new GetUpdateQuickFilters(true));
} else {
if (this.state.itemsArray.length > 1) {
for (let i = this.state.itemsArray.length; i >= 0; i--) {
if (this.state.itemsArray[i] === legendItem.text) {
this.state.itemsArray.splice(i, 1);
break;
}
}
const query = new QueryParam(EQueryEntity.Connection, 'connectionType', this.itemsArray, EQueryOperator.NotIn, 'QuickFiltersComponent');
AppStore.instance.dispatch(new AddQuickQueryParamsAction([query]));
AppStore.instance.dispatch(new GetUpdateQuickFilters(true));
} else {
AppStore.instance.dispatch(new ClearQuickQueryParamsAction());
}
}
originalDoughnutLegendBehavior.call(this, e, legendItem);
};
}
you can use componentDidMount method only to fetch the list and then in componentDidUpdate method you can update that list with the filter and it will re-render your component with the latest data.

Updating arrays in react component state

In the function below, I am trying to update the state of a react component; the animalMix item is an array. I take a copy, update it and then try to overwrite the original. I have checked that the new array (newAnimalsHeld) is updated correctly, but this is not reflected when i set animalMix in state equal to it.
The whole thing can be seen in context here:
https://codepen.io/timsig/pen/XVdbdo?editors=0010
Many thanks for any help.
removePair(){
console.log('Match!');
console.log(this.flipped);
let animalsHeld = [...this.state.animalMix];
let theMatch = this.flipped[0].props.animal;
let newAnimalsHeld = animalsHeld.map(function(animal){
if (animal.animal === theMatch) {
console.log('MATCH! Animal: ' + animal.animal + 'Match:' + theMatch);
return {};
}else{
return animal;
}
});
console.log('New Animals held: ', newAnimalsHeld);
this.setState({
animalMix: newAnimalsHeld,
doTurn: true
});
this.flipped = [];
console.log(this.state.doTurn, this.state.animalMix);
}
setState is an asynchronous function. However, you can print to console after state has updated in the following manner:
this.setState({
animalMix: newAnimalsHeld,
doTurn: true
},() => {console.log(this.state.doTurn, this.state.animalMix);});

Update nth item in array in state in parent from child in React?

In my top level component I have a function to update state. I pass this down to different child elements so that they can update my main state.
In my top level component:
updateValue(item, value) {
this.setState({[item]: parseInt(value)});
}
This has worked so far however now I need to update the nth item in an array.
My top level state is like this:
this.state = {
chosenExercises: [
'Bench press',
'Squat',
'Pull up'
]
};
And in my child component Im trying to do something like:
this.props.updateValue('chosenExercises'[1], 'New exercise');
So that my state would then be:
this.state = {
chosenExercises: [
'Bench press',
'New exercise',
'Pull up'
]
};
Am I going about this the correct way? Or should my state be key value pairs?
this.state = {
chosenExercises: {
0: 'Bench press',
1: 'New exercise',
2: 'Pull up'
}
};
This would potentially solve some of my problems of making the exercises easier to target but Im not sure which is best practice.
Since the chosenExercises can be multiple it makes sense to make it as an array, however you need to update your state differently. Instead of passing the index of the array element to update, you should actually make a copy of the array, update it in the child element and then send the updated array to the parent.
You could do something like:
In Child:
updateValue = (item, index, value) => {
let newValue = [...this.props[item].slice(0, index), value, ...this.props[item].slice(index + 1)];
this.props.updateValue(item, newValue);
}
The thing with this is that your state has to remain immutable so you have to provide a new Array to update in your state. So you'll end up with something like:
this.updateValue('chosenExercises', 1, 'New exercise');
updateValue(item, index, value) {
const newArray = this.state[item].slice();
newArray[index] = value;
this.setState({ [item]: newArray });
}
The array.slice() function creates a new Array, in which you update the value by its index. Afterwards you update your component state with the new array.
If you happen to do this more often, React created an immutability helper for these things. You can read more about it here. This would let you do something like:
import update from 'react-addons-update';
this.setState({
[item]: update(this.state[item], {[index]: {$set: value } })
});
It can be done with this in the top level component:
updateValue(item, value, options) {
if (options.isChosenExercises === true) {
this.setState((prevState) => {
let newchosenExercises = prevState.chosenExercises.slice();
newchosenExercises[item] = value;
return {chosenExercises: newchosenExercises};
});
} else {
this.setState({[item]: parseInt(value)});
}
}
For normal uses pass an empty object as the last parameter:
this.props.updateValue('setLength', e.target.value, {})}
But when you want to update the exercise array pass an object with isExercises set to true.
chooseThisExercise() {
this.props.updateValue(numberInTheArrayToChange, newExercise, {isChosenExercises: true});
}

Resources