setState() in Reactjs updating multiple properties instead of one - javascript

When I update 'classProficienciesChoices' in my state using setState() it is updating not only that property, but also where I derived the 'classProficienciesChoices' info from in the 'classSelected' property, AND ALSO from where I derived the classSelected info from in the 'classesInfo' property.
The same function I update 'classProficienciesChoices' I also update 'classProficiencies', and it updates properly in the one property I tell it to, and not the elements where the information was derived from.
Any insight would be helpful. The Create component has other components nested and none of them have state and only use props passed. There are navigation, selection, and information display components nested.
import React, { Component } from 'react'
import Create from './Create'
class App extends Component {
constructor(props) {
super(props);
const url = 'http://www.dnd5eapi.co/api/';
fetch(url + 'classes')
.then(result => result.json())
.then(result => { this.setState({ classes: result, }, this.getInfo(result)) });
}
state = {
classes: {}, //assigned value from API call in constructor
classesInfo: [], //assigned value from API call in getInfo()
classSelected: {}, //assigned a value derived from classInfo in displayClassInfo()
classProficiencies: [], //assigned a value derived from classSelected in setStartingProficiencies()
classProficienciesChoices: [], //assigned a value derived from classSelected in setStartingProficiencies()
}
getInfo(data) {
let info = []
const url = 'http://www.dnd5eapi.co'
for (var i = 0; i < data.results.length; i++) {
fetch(url + data.results[i].url)
.then(result => result.json())
.then(result => info.push(result))
}
this.setState({ classesInfo: info, })
}
}
setStartingProficiencies(chosenClass) {
const profs = chosenClass.proficiencies.map((prof) => {
return prof;
});
const proChoice = chosenClass.proficiency_choices.map((choices) => {
return choices;
});
this.setState({ classProficiencies: profs, classProficienciesChoices: proChoice, });
}
addProficiency = (proficiencyName) => {
const { classProficienciesChoices } = this.state
// classProficienciesChoices: [
// { choose: 2, type: 'proficiencies', from: [{ name: 'someName', url: 'someUrl' }, { name: 'someName', url: 'someUrl' }] },
// ]
// different classes have more objects in the parent array
let newChoiceArray = classProficienciesChoices.map((choices) => {
return choices
})
for (var i = 0; i < newChoiceArray.length; i++) {
for (var j = 0; j < newChoiceArray[i].from.length; j++) {
if (newChoiceArray[i].from[j].name === proficiencyName) {
let newChoices = newChoiceArray[i].from.filter(function (proficiency) { return proficiency.name !== pIndex })
let newProficiency = newChoiceArray[i].from.filter(function (proficiency) { return proficiency.name === pIndex })
newChoiceArray[i].from = newChoices //I think this is the problem
this.setState(state => ({
classProficiencies: [...state.classProficiencies, newProficiency[0]],
proficienciesChoices: newChoiceArray,
}))
}
}
}
}
displayClassInfo = index => {
const { classesInfo } = this.state
for (let i = 0; i < classesInfo.length; i++) {
if (classesInfo[i].index === index) {
const classSelected = classesInfo.filter(function (cClass) { return cClass.name === classesInfo[i].name })
this.setState({ classSelected: classSelected[0], isClassSelected: true }, this.setStartingProficiencies(classSelected[0]),)
break;
}
}
}
render() {
const { classes, classesInfo, classSelected, isClassSelected, classProficiencies, classProficienciesChoices } = this.state
return (<Create classes={classes} classesInfo={classesInfo} displayClassInfo={this.displayClassInfo} classSelected={classSelected} isClassSelected={isClassSelected} category='classes' classProficiencies={classProficiencies} classProficienciesChoices={classProficienciesChoices} addProficiency={this.addProficiency} />);
}
}
export default App

You call setState four times, of course it will update the state multiple times.

SOLVED!!! Stumbled across turning the array into a string and then back. It breaks the reference and creates an entirely new array. Reference type got me.
setStartingProficiencies(chosenClass) {
const proficiencies = JSON.parse(JSON.stringify(chosenClass.proficiencies))
const proficienciesChoices = JSON.parse(JSON.stringify(chosenClass.proficiency_choices))
this.setState({ classProficiencies: proficiencies, classProficienciesChoices: proficienciesChoices, });
}

Related

Trouble with array and state

Can someone explain, why I'm getting an error, when I'm removing items from an array? It works once, but then it crashes. Checked - is boolean meaning.
removeCards = () => {
console.clear();
for (let i = 0; i < this.state.cards.length; i++) {
console.log(this.state.cards[i]);
if (this.state.cards[i].checked) {
delete this.state.cards[i];
}
}
this.setState({ cards: this.state.cards });
};
In React, state is immutable. So instead of trying to alter it directly, create a copy of it and then apply that to state -
removeCards = () => {
console.clear();
const newCards = [];
for (let i = 0; i < this.state.cards; i++) {
if (!this.state.cards[i].checked) {
newCards.push(this.state.cards[i];
}
}
this.setState({ cards: newCards });
};
Maybe my trouble in that? That's how i'm chaning states from true to false and in reverse.
myFunc = (props) => {
let num = Number(props);
num--;
let cards = [...this.state.cards];
if (this.state.cards[num].checked) {
cards[num] = { ...cards[num], checked: false };
} else {
cards[num] = { ...cards[num], checked: true };
}
this.setState({ cards });
};

React js - setState and array of objects

I'm new to React, so I'm struggling with this:
why does setting state as a single object array works, but getting the array from state, pushing to it, and setting as state again doesn't work?
This is the working code. I have this IMAGE array in as state, not initialized:
class PersonalBananas extends React.Component {
constructor(){
super();
this.state = {
username: 0,
images: 0,
pred: 0,
IMAGES: 0
}
this.imgList = this.imgList.bind(this)
}
In componentDidMount I call the imgList():
componentDidMount() {
let $this = this;
axios.get('http://localhost:8081/auth/username')
.then((response) => {
let uname = response.data;
this.setState({
username: uname
});
})
axios.get('http://localhost:8081/auth/files')
.then((response) => {
let imgs = response.data;
console.log("images response: "+imgs);
this.setState({
images: imgs
},
function() { $this.imgList() }
);
})
}
Inside imgList() I call getImgPred:
imgList = () => {
var $this = this;
const IMAGES = [];
const imgpaths = this.state.images;
console.log("images from imgLis():"+imgpaths);
for (let i = 0; i < imgpaths.length; i++) {
var path = imgpaths[i]
this.getImgPred(path);
console.log("pred:"+$this.state.pred);
}
console.log("IMAGES from imgLis():"+IMAGES);
};
And here finally I call the IMAGESpush():
getImgPred = (path) => {
var username = this.state.username;
var $this = this;
let regex = new RegExp(username+'\/(.*?)$');
let imgRegex= /{username}\/(.*?)$/;
let filename = regex.exec(path);
console.log("filename at front:"+filename[1]);
console.log("regex1:"+imgRegex+", regex2:"+regex);
axios.post('http://localhost:8081/auth/imgpred',
"filename=" + filename[1]
).then(function (response) {
console.log("response at front (get img prediction):"+response.data);
if (response.status === 200) {
$this.setState({
pred: response.data
}, function() { $this.IMAGESpush(path) } );
}
});
}
Here is the problem: when I just initialize the const IMAGES = [], push to it, then set state - it works fine. What i'm trying to do is: const Images = this.state.IMAGES. I can't do it.
IMAGESpush = (path) => {
var $this = this;
const IMAGES = [];
IMAGES.push({
src: process.env.PUBLIC_URL +`/${path}`,
thumbnail: process.env.PUBLIC_URL +`/${path}`,
thumbnailWidth: 320,
thumbnailHeight: 320,
caption: $this.state.pred
})
this.setState({
IMAGES: IMAGES
})
}
render() {
return (
<div>
<Gallery images={this.state.IMAGES}/>
</div>
)
}
}
export default PersonalBananas;
Pushing to an array does not change its reference, it's essentially the same object to React's reconciliation algorithm (which doesn't deeply compare objects inside of its state).
What you should really do is creating a new array, expanding the old one into it and adding a new element on top of that. Like this:
this.setState({
IMAGES: [...this.state.IMAGES, { /* new object here */ }]
});
You can also use the concat function to merge arrays, since it doesn't change any array - it returns a new array instead.
this.setState({
IMAGES: this.state.IMAGES.concat([{ /* new object here */ }])
});

How to sum the value only once in react redux

I would like to know in my scenario: After receiving props, the function gets called in the compoentdidupdate method, in which am summing up the amount if id is same, But it keeps on adding the values multiple times on load. how to resolve this.
class Data extends React.PureComponent{
constructor(props){
super(props);
this.state={
total: "";
}
}
componentDidMount() {
this.callFetch();
}
callFetch(){
this.props.dispatch(getData("all"));
this.props.dispatch(newData("new"));
}
componentDidUpdate(){
const { alldata, newdata } = this.props.query;
const obj =[...alldata, ...newdata];
const result=[];
if(obj.length > 0) {
obj.forEach(function (o) {
var existing = result.filter(function (i) { return i.id=== o.id})[0];
if (!existing)
result.push(o);
else
existing.amount+= o.amount;
});
this.setState({total: result})
}
}
render(){
return(
this.state.total.length > 0 ?
this.state.total.map(e=>{
<div>price:{e.amount}</div>
}) : ""
)
}
props am receiving is below, but in output am receiving 1200, and keeps on increasing,
alldata= [{
id: 1,
amount: 200
}, {
id: 2,
amount: 400
}]
newdata= [{
id: "1",
amount: 400
}]
expected output:
price: 600
price: 400
Hey #miyavv Have a look at the below piece of code. Comparing prevProps and current props should help. It is general technique used in reactjs to solve non-needed runs in componentDidUpdate.
class Data extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
total: ""
};
}
componentDidMount() {
this.callFetch();
}
callFetch() {
this.props.dispatch(getData("all"));
this.props.dispatch(newData("new"));
}
componentDidUpdate(prevProps) {
const { alldata, newdata } = this.props.query;
const obj = [...alldata, ...newdata];
const result = [];
// new Line added
const { alldata: prevAllData, newdata: prevNewData } = prevProps.query;
if (prevAllData !== alldata || prevNewData !== newdata) {
if (obj.length > 0) {
obj.forEach(function(o) {
var existing = result.filter(function(i) {
return i.id === o.id;
})[0];
if (!existing) result.push(o);
else existing.amount += o.amount;
});
this.setState({ total: result });
}
}
}
render() {
return this.state.total.length > 0
? this.state.total.map(e => {
<div>price:{e.amount}</div>;
})
: "";
}
}
Let me know if it helps !!. Thanks
That’s because you are dispatching two actions which will result to call componentDidUpdate multiple times. And in your componentDidUpdate method there’s no conditioning telling that your data is already fetched.
You could call only one action that will further dispatch two actions you are dispatching in callFetch method. You could also keep a flag which will tell whether the data is yet fetched or not.
If you are using redux-thunk, than it can be easily achieved. You can define one action which will be dispatched in callFetch method. And in your newly defined action you can dispatch your mentioned two actions.

Assign return value of function to object property in React Native

export default class Leaderboard extends React.Component {
constructor(props) {
super(props);
this.numLeaders = 10;
this.topPlayers = populateLeaderboard(this.numLeaders);
this.state = {
tableHead: ['', 'Name', 'Age', 'School', 'Points'],
//Trying to assign leaders array to tableTitle
tableTitle: (function(this.numLeaders) {
let leaders = [];
for (let i = 0; i < numLeaders; i++) {
leader.push(i);
}
return leaders;
}())
};
}
So I'm trying to generate an array within a function and then assign it as a return value to an object property. I'm not sure if I'm messing up with Javascript or React, I don't have a ton of practice with Javascript objects.
If you want to do it in your way, here it is:
this.state = {
tableHead: ["", "Name", "Age", "School", "Points"],
tableTitle: (() => {
const leaders = [];
for (let i = 0; i < this.numLeaders; i++) {
leaders.push(i);
}
return leaders;
})(),
};
}
A working example.
const state = {
tableTitle: (() => {
const leaders = [];
for (let i = 0; i < 10; i++) {
leaders.push(i);
}
return leaders;
})(),
};
console.log(state.tableTitle);
I skipped the argument part here since you can already reach numleaders. Why do you need an argument?
Also, I used an arrow function above to avoid binding this. If you stick to your version don't forget to bind it:
this.state = {
tableHead: ["", "Name", "Age", "School", "Points"],
tableTitle: function () {
const leaders = [];
for (let i = 0; i < this.numLeaders; i++) {
leaders.push(i);
}
return leaders;
}.bind(this)(),
};
Or, if you really want to stick to your original-original version :) you can try this one:
this.state = {
tableTitle: (function (numLeaders) {
const leaders = [];
for (let i = 0; i < numLeaders; i++) {
leaders.push(i);
}
return leaders;
}(this.numLeaders)),
};
You are passing the argument then getting it back. No need to bind this here since you are not using this.numLeaders in your function.
Here is an alternative with some spread syntax and keys.
this.state = {
tableHead: ["", "Name", "Age", "School", "Points"],
tableTitle: [...Array(this.numLeaders).keys()],
};
A working example:
const state = {
tableTitle: [...Array(10).keys()],
};
console.log(state.tableTitle);
But, if this data is not dynamic maybe the state is not a suitable place for it.

TypeError: Cannot read property 'style' of undefined

export class EstimateForm extends React.Component<IEstimateFormProps,
IEstimateFormState> {
state: IEstimateFormState = {
cellUpdateCss: 'red',
toRow: null,
fromRow: null,
estimateList: null,
estimateItemList: [],
poseList: null,
levelList: null,
partList: null,
selectedEstimate: null,
totalEstimateItems: 0,
selectedIndexes: [],
totalEstimateAmount: 0,
grid: null,
projectId: 0,
};
constructor(props, context) {
super(props, context);
this.state.estimateList = this.props.estimateList;
}
rowGetter = i => {
const row = this.state.estimateItemList[i];
const selectRevison = this.state.selectedEstimate.revision;
if (row['pose.poseName']) {
const poseCode =
row['pose.poseName'].substring(row['pose.poseName'].lastIndexOf('[') + 1,
row['pose.poseName'].lastIndexOf(']'));
for (const pose of this.state.poseList) {
if (pose.poseCode === poseCode) {
row.pose = pose;
}
}
}
if (row['level.levelName']) {
const levelCode = row['level.levelName'].substring(
row['level.levelName'].lastIndexOf('[') + 1,
row['level.levelName'].lastIndexOf(']')
);
for (const level of this.state.levelList) {
if (level.levelCode === levelCode) {
row.level = level;
}
}
}
if (row['level.part.partName']) {
const partCode = row['level.part.partName'].substring(
row['level.part.partName'].lastIndexOf('[') + 1,
row['level.part.partName'].lastIndexOf(']')
);
for (const part of this.state.partList) {
if (part.partCode === partCode) {
row.part = part;
}
}
}
row.get = key => eval('row.' + key);
row.totalCost = (row.materialCost + row.laborCost) * row.amount;
const changeColor = {
backgroundcolor: 'red'
};
const all = document.getElementsByClassName('react-grid-Row') as
HTMLCollectionOf<HTMLElement>;
debugger; if (row.revision > selectRevison) {
for (let i = this.state.fromRow; i <= this.state.toRow; i++) {
all[i].style.color = 'red'; //HERE
}
return row;
}
}
handleGridRowsUpdated = ({ fromRow, toRow, updated }) => {
const rows = this.state.estimateItemList.slice();
for (let i = fromRow; i <= toRow; i++) {
const rowToUpdate = rows[i];
const updatedRow = update(rowToUpdate, { $merge: updated });
rows[i] = updatedRow;
}
this.setState({ estimateItemList: rows, fromRow: (fromRow), toRow: (toRow)
}, () => {
});
};
saveEstimateItems = () => {
if (this.state.selectedEstimate == null) {
toast.warn(<Translate
contentKey="bpmApp.estimateForm.pleaseSelectEstimate">Please select an
estimate</Translate>);
return;
}
render() {
return ()
}
I wanna to change the row color when the condition row.revision > this.state.selectedEstimate.revision . How can I prevent the change of this.color. However TypeError: Cannot read property 'style' of undefined get error but row color is not change. how can i solve this problem it is my first project in react and i dont know where is the problemThanks for your feedback guys.
Okay, so without the rest of the context because your pasted code is difficult to read and understand, the simplest reason for your issue is in this chunk:
const all = document.getElementsByClassName('react-grid-Row') as
HTMLCollectionOf<HTMLElement>;
debugger; if (row.revision > selectRevison) {
for (let i = this.state.fromRow; i <= this.state.toRow; i++) {
all[i].style.color = 'red'; //HERE
}
Essentially there's multiple things that could go wrong here, but most likely there are either no rows with that class on the page, or less than your this.state.fromRow, I see you've got the debugger in there, but you are missing a few things:
You aren't doing a null check on all to make sure you are finding something
You aren't checking whether all.length > this.state.fromRow
You aren't breaking the for loop if all.length < this.state.toRow
It's failing because all[i] doesn't exist, or there's no values:
all = [0, 1]
and you are looking for all[3] for example
Throw in those fallbacks and check what all is on page load and you should be able to figure it out.

Categories

Resources