React js - setState and array of objects - javascript

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 */ }])
});

Related

how to merge arrays from 2 different arrays

this is an image from the API that returns the test results before and after each other.
Initially when the user has not entered the test result, the array result = null, After the user enters, the array will have the same result as the image below:
my problem is after user input test, and then we update field result_template can be add or remove subjects so how do i merge subjects if use edit again result_template,
If there are new subjects but no scores are available, the default is set = 0
You can watch the video for better understanding: link
desired result: image
here is my code:
const { data } = await this.$store.dispatch(
`${UserGeneral.STORE_ADMIN_USER_KEY}/getDetailSchoolResults`,
this.$route.params.id
);
const formData = data.data;
if (formData.result) {
const listResultTemplate = formData.result.result.map((item) => {
let data = {
title: item.title,
subjects: [],
files: [],
};
item.files.forEach((file) => {
data.files.push(file);
});
item.subjects.forEach((subject) => {
data.subjects.push({
name: subject.name,
scores: {
total_score: subject.scores.total_score,
medium_score: subject.scores.medium_score,
private_score: subject.scores.private_score,
},
});
});
return data;
});
this.result = listResultTemplate;
} else {
const listResultTemplate = formData.result_template.result_template.map(
(item) => {
let data = {
title: item.title,
subjects: [],
files: [],
};
item.subjects.forEach((subject) => {
data.subjects.push({
name: subject,
scores: {
total_score: 0,
medium_score: 0,
private_score: 0,
},
});
});
return data;
}
);
this.result = listResultTemplate;
}
thanks for your help !

Setting react state with nested objects from JSON fetch call

I am fetching recipes from a recipe app and id like to insert certain objects from the returning json result onto my state with setstate. I know how to do one of these but im having trouble figuring out how to map the results on to my state. Can anyone help me on this?
The code for the issue is here. I have changed my api key and code for security
componentDidMount() {
let url = `https://api.edamam.com/search?q=banana&app_id=chjhvje1&app_key=b67djhhvhvhaef`;
fetch(url)
.then((response) => {
return response.json();
})
.then((data) => {
let recipeUIState = [ ...this.state.RecipeUI ];
recipeUIState[0].title = data.hits[0].recipe.label;
recipeUIState[0].thumbnail = data.hits[0].recipe.image;
recipeUIState[0].href = data.hits[0].recipe.url;
this.setState({ RecipeUI: recipeUIState });
console.log(data.hits[0].recipe);
});
}
State is as follows-
export default class RecipeUI extends Component {
constructor(props) {
super(props);
this.state = {
food: '',
RecipeUI: [ { title: '' } ]
// thumbnail: '', ingredients: '', href: ''
};
this.search = this.search.bind(this);
}
reponse from API is attached as image
data.hits.forEach(({ recipe }) => {
// We get the original state every before it's updated in the iteration
const recipeUIState = [...this.state.RecipeUI];
// Check if there's an existing recipe with the same title
const idx = recipeUIState.findIndex(r => r.title === recipe.title);
// Helper object to create a recipe from the current iteration
const currentRecipe = {
title: recipe.label,
thumbnail: recipe.image,
href: recipe.url
};
// `findIndex` returns -1 if no entry was found, otherwise it returns the index
if (idx < 0) {
// No existing recipe was found, append the new recipe to the original state
return this.setState({
recipeUIState: [...recipeUIState, ...currentRecipe]
});
}
// Recipe already existed, create a new recipe by overwriting
// the object at the index we found earlier
const newRecipeUIState = {
...recipeUIState[idx],
...currentRecipe
};
// Replace the recipe at found index
recipeUIState[idx] = newRecipeUIState;
this.setState({ recipeUIState });
});
Something like this? could probably be simplified using Array#reduce but I don't feel too comfortable using it.

setState() in Reactjs updating multiple properties instead of one

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, });
}

Converting Calories Tracking application to ES6 Classes

I managed to recreate a MVC calorie tracker app from a course and I am trying to convert it to ES6 classes now.
I am a little stuck in understanding how to call the methods in the Module inside the Controller to return the items I need.
class Item {
constructor() {
this.data = {
items: [{
name: 'Salad',
calories: 200,
id: 0
},
{
name: 'Eggs',
calories: 500,
id: 1
}],
totalCalories: 0,
currentItem: null
}
};
getItems() {
return this.data.items
};
logData = () => {
console.log(data.items);
};
}
class App {
constructor(Item, UI) {
this.Item = Item;
this.UI = UI;
}
init() {
const items = Item.getItems();
UI.populateItemList(items)
}
}
const application = new App(new Item(), new UI())
When I try to call Item.logData() in the console it gives me TypeError: this.data is undefined.
I researched online and it seems that the method I declared is for the constructor only. How would I go about declaring methods that I'll use in the Controller or in any other class, just like I did below by returning a method out of the constructor?
What Im trying to convert initially looks like this:
const ItemCtrl = (function () {
const Item = function (id, name, calories) {
this.name = name;
this.id = id;
this.calories = calories;
}
const data = {
items: StorageCtrl.getStorage(),
totalCalories: 0,
currentItem: null
}
return {
getItems: function () {
return data.items
},
logData: function () {
return data;
}
}
const App = (function (ItemCtrl, StorageCtrl, UICtrl) {
return {
init: function () {
const items = ItemCtrl.getItems();
UICtrl.populateItems(items);
}
}
})(ItemCtrl, StorageCtrl, UICtrl);
App.init();
You need to initialise the controller first:
class App {
constructor(Item, UI) {
this.item = new Item();
this.UI = new UI();
}
init() {
const items = this.item.getItems();
this.UI.populateItemList(items)
}
}

Merge two objects by matching key while following Flow rules

I have created a function as shown below, which should append the array value into the first object.
Let's say we have given data below:
subjects = {
student1: ['Math', 'Science'],
student2: ['Math', 'Physics', 'English'],
};
students = {
student1: {
// other data
subjectsList: [],
},
student2: {
// other data
subjectsList: [],
},
};
Function code below:
const merge = (subjects: Object, students: Object) => {
Object.keys(subjects).forEach((id: Object) => {
const subjectsList = subjects[id];
const student = students[id];
if (student) {
const updatedStudent = {
...student,
subjectsList,
};
students[id] = updatedStudent;
}
});
return students;
};
This would result in a flow error:
Cannot access the computed property using object type [1].
app/reducers/subjects.reducer.js:42:32
42| const student = students[id];
^^
References:
app/reducers/subjects.reducer.js:40:48
40| Object.keys(subjects).forEach((id: Object) => {
^^^^^^ [1]
Object.keys(subjects).forEach((id: Object)
The id in the .forEach((id) => is not an Object, but a String
If you remove the typehinting (or whatever it is called).
const merge = (subjects, students) => {
Object.keys(subjects).forEach((id) => {
const subjectsList = subjects[id];
const student = students[id];
if (stand) {
const updatedStudent = {
...student,
subjectsList,
};
students[id] = updatedStudent;
}
});
return students;
};
I think this will work.

Categories

Resources