Function just re render one time - javascript

I got a problem with this Function. When I trigger this function it only re render the component the first trigger. After that not any more. I cant find the problem :(
function selectAnswer(id, questId) {
let newArr = questions
for(let i = 0; i < newArr.length; i++){
if(questId === newArr[i].id){
const changedAnswers = newArr[i].answers.map(answer => {
return answer.id === id ?
{...answer, selected: !answer.selected} :
{...answer, selected: false}
})
newArr.forEach(element => {
if(questId === element.id){
element.answers = changedAnswers
}
})
}
}
setQuestions(newArr)
}

You're never actually updating the state. This doesn't create a copy of the array, it just duplicates a reference to the array:
let newArr = questions
So this isn't setting state to a new array, but just a reference to the same array:
setQuestions(newArr)
Additionally, instead of creating new state, you are mutating the existing state:
element.answers = changedAnswers
Start by creating a new array, even if it contains the same elements as the original:
let newArr = [...questions];
Then, when you want to modify one of those elements, instead of modifying the existing element you would instead replace it with a new one. So instead of this structure:
newArr.forEach(element => {
});
You could instead replace your new array with a .map() over itself:
newArr = newArr.map(element => {
});
And within that .map() you would return either the unmodified object or the replacement object:
newArr = newArr.map(element => {
if(questId === element.id) {
return {...element, answers: changedAnswers};
} else {
return element;
}
});
Overall, the idea here is that you don't want to loop over an existing array, modify its values, and set that array back to state. That's not "updating state" in the React sense but instead it's "mutating state". Instead, create a new array and populate it with the objects you want. Those objects can be a mix of unchanged objects from the existing array and replaced (not modified) objects from the existing array.

Related

How do I search array within an object for any instances of specified string values?

How do I search an array for any instances of multiple specified string values?
const arrayOfObjects = [{
name: box1,
storage: ['car', 'goat', 'tea']
},
{
name: box2,
storage: ['camel', 'fox', 'tea']
}
];
arrayOfSearchItems = ['goat', 'car', 'oranges'];
If any one or all of the arrayOfSearchItems is present in one of the objects in my array, I want it to either return false or some other way that I can use to excluded that object that is in my arrayOfObjects from a new, filtered arrayOfObjects without any objects that contained the arrayOfSearchItems string values. In this case I would want an array of objects without box1.
Here is what I have tried to do, based on other suggestions. I spent a long time on this. The problem with this function is that it only works on the first arrayOfSearchItems strings, to exclude that object. It will ignore the second or third strings, and not exclude the object, even if it contains those strings. For example, it will exclude an object with 'goat'. Once that happens though, it will no longer exclude based on 'car'. I have tried to adapt my longer code for the purposes of this question, I may have some typos.
const excludeItems = (arrayOfSearchItems, arrayOfObjects) => {
let incrementArray = [];
let userEffects = arrayOfSearchItems;
let objects = arrayOfObjects;
for (i = 0; i < userEffects.length; i++) {
for (x = 0; x < objects.length; x++) {
if (objects[x].storage.indexOf(userEffects) <= -1) {
incrementArray.push(objects[x]);
}
}
}
return(incrementArray);
}
let filteredArray = excludeItems(arrayOfSearchItems, arrayOfObjects);
console.log(filteredArray);
Thanks for providing some example code. That helps.
Let's start with your function, which has a good signature:
const excludeItems = (arrayOfSearchItems, arrayOfObjects) => { ... }
If we describe what this function should do, we would say "it returns a new array of objects which do not contain any of the search items." This gives us a clue about how we should write our code.
Since we will be returning a filtered array of objects, we can start by using the filter method:
return arrayOfObjects.filter(obj => ...)
For each object, we want to make sure that its storage does not contain any of the search items. Another way to word this is "every item in the starage array does NOT appear in the list of search items". Now let's write that code using the every method:
.filter(obj => {
// ensure "every" storage item matches a condition
return obj.storage.every(storageItem => {
// the "condition" is that it is NOT in the array search items
return arrayOfSearchItems.includes(storageItem) === false);
});
});
Putting it all together:
const excludeItems = (arrayOfSearchItems, arrayOfObjects) => {
return arrayOfObjects.filter(obj => {
return obj.storage.every(storageItem => {
return arrayOfSearchItems.includes(storageItem) === false;
});
});
}
Here's a fiddle: https://jsfiddle.net/3p95xzwe/
You can achieve your goal by using some of the built-in Array prototype functions, like filter, some and includes.
const excludeItems = (search, objs) =>
objs.filter(({storage:o}) => !search.some(s => o.includes(s)));
In other words: Filter my array objs, on the property storage to keep only those that they dont include any of the strings in search.

Unable manage objects in array independently

I'm working on a text editor using React and I want to keep track of the changes in an array. Whenever I make changes an object is added to the array (as it should) but all the other objects change as well and become the same as the new one. I'm aware of how Javascript doesn't store objects in independent variables if not reassigned so I used the spread operator to create a new array and then add a new object using Object.assign() but it's still not working and I can't figure out what I'm doing wrong.
getNewChangesHistory(update, oldChangesHistory){
var newChangesHistory = [...oldChangesHistory, Object.assign({}, update)];
if(newChangesHistory.length > 25){
delete(newChangesHistory[26]);
}
return newChangesHistory;
}
updateDocumentContent(content){
var newDocument = {...this.state.document};
newDocument.content = content;
this.setState(prevState => {return {
document: newDocument,
changesHistory: this.getNewChangesHistory(content, prevState.changesHistory),
hasChanges: true
}})
}
updateTextbox(editedProperties, key){
const newDocumentContent = {...this.state.document.content};
newDocumentContent.textboxes[key] = { //Textboxes are stored as objects of an array
...editedProperties,
id: key
}
this.updateDocumentContent(newDocumentContent)
}
render(){
return(
<TextBox
onEdit={(editedProperties) => {this.updateTextbox(editedProperties, 0)}}
/>
)
}
The problem is in updateTextbox. With {...this.state.document.content} it only creates a shallow copy. In this copy the textboxes property will still reference the same object. And you mutate that object by the assignment to its [key] property. So that mutation will be seen in all objects that have that same textboxes object reference.
One way to get rid of this, is to treat textboxes as immutable, and do this:
updateTextbox(editedProperties, key){
const {content} = this.state.document;
const newDocumentContent = {
...content,
// create a new textboxes array
textboxes: Object.assign([], content.textboxes, {
[key]: {
...editedProperties,
id: key
}
})
};
this.updateDocumentContent(newDocumentContent);
}

best way to change value of an object from an array of objects when given a key

I'm working (learning) in React. I have an array of objects which hold info on gamertags. They are rated with up to five stars and this function is called when a user clicks on the stars in the GUI to alter the rating.
my solution:
I make a copy of state, iterate over the copy, check each entry for the key, reassign the number of stars, then use my setState hook to assign the altered array.
Is there a more concise way to do this? I searched all over stack and google and couldn't find anything. I feel like I should be able to map, use an arrow function and or a ternary. Thanks for any comments on styles, JS and ES6 seems to be all about that. Thx fam.
function changeStars(stars, key) {
console.log(stars, key);
const newRatingInventory = [ ...tagInventory];
for (const [index] of newRatingInventory.entries()) {
if (newRatingInventory[index].timeStamp === key) {
newRatingInventory[index].stars = stars;
}
}
setTagInventory([...newRatingInventory]);
Using the spread syntax doesn't creates a deep copy - it just creates a new array but the objects are not cloned. So any changes made to any object inside the new array will mutate the original object.
Currently you are mutating the state directly which is not the correct way to update the state in React.
You should use the .map() method to iterate over the array, create and return a new object if the condition newRatingInventory[index].timeStamp === key evaluates to true.
function changeStars(stars, key) {
const newState = tagInventory.map(obj => {
if (obj.timeStamp === key) {
// return a new object with the updated
// value of "stars" property.
return { ...obj, stars };
}
// if the above condition is not true,
// return the current object as it is.
return obj;
});
// update the state
setTagInventory(newState);
}
There is a multipal way to do this
my recommendation one is map.
const changeStars = (stars, key) => {
let tempRating = tagInventory && tagInventory.length > 0 &&
tagInventory.map(item => item.timeStamp === key ? {...item, stars} : item);
setTagInventory(tempRating)
}

Array/map like data structure with unique ID support for items, O(1) get() method and no duplication

I have a lot of data that I store in arrays. All array items have a string unique ID (like 123e4567-e89b-12d3-a456-426614174000). Before pushing an item, I call findIndex to make sure no duplicates are inserted with the same ID. I frequently access array items by index and by ID (find(i => i.id === UUID))
Is there a more efficient data structure for this? Supporting these features:
ordered items (should support push, unshift)
get item by ID in O(1) time (faster find)
get item index by ID in O(1) time (faster findIndex)
get items by index (arr[index])
no duplication by ID (#2 will make this easy to support)
easy iteration (for..of / forEach)
sorting items
I looked into a regular object and a Map but they don't support accessing items by index or sorting.
Using an object or Map indexed by ID will work fine. Accessing an object key will be O(1), as is Map.has.
In order to get the item index in O(1) as well without iterating over the object or Map again, create another data structure mapping IDs to their index.
const itemsById = new Map();
const itemsByIndex = new Map();
const addItem = (item) => {
if (itemsById.has(item.id)) return; // duplicate already exists
itemsById.set(item.id, item);
itemsByIndex.set(itemsByIndex.size, item);
};
Maps can be iterated over with for..of. Objects can be iterated over with .entries().
To "sort", put the values into an array, sort the array, then change itemsByIndex as needed.
Edit: you noted in a comment you don't want to sort by UUID. Well be default everything will be sorted by insertion order, and if you wanted to sort by another value you would push that value to the sort array instead of "data.id".
I am upvoting the above answer, because you will have to use a couple different data structures to accomplish what you want. Here is a way to implement this in one Javascript object:
const myDictionary = function() {
let sort = [];
const indexes = new Map();
const dictionary = new Map();
this.addItem = function(data) {
indexes.set(data.id, sort.push(data.id) - 1);
dictionary.set(data.id, data);
}
this.getItemByID = function(key) {
return dictionary.get(key);
}
this.getItemByIndex = function(i) {
return dictionary.get(sort[i]);
}
this.getItemIndex = function(id) {
return indexes.get(id);
}
this.sortItems = function(sortingAlgorithm) {
if (sortingAlgorithm) {
sort = sortingAlgorithm(sort);
} else {
function quickSortBasic(array) {
if(array.length < 2) {
return array;
}
var pivot = array[0];
var lesserArray = [];
var greaterArray = [];
for (var i = 1; i < array.length; i++) {
if ( array[i] > pivot ) {
greaterArray.push(array[i]);
} else {
lesserArray.push(array[i]);
}
}
return quickSortBasic(lesserArray).concat(pivot, quickSortBasic(greaterArray));
}
sort = quickSortBasic(sort);
}
sort.forEach(function (el, i) {
indexes.set(el, i);
});
}
}
const dictionary1 = new myDictionary();
dictionary1.addItem({id: "1", data:"data 1"});
dictionary1.addItem({id: "4", data:"data 4"});
dictionary1.addItem({id: "2", data:"data 2"});
console.log(dictionary1.getItemIndex("4"));
dictionary1.sortItems();
console.log(dictionary1.getItemIndex("4"));
console.log(dictionary1.getItemByID("2"));
console.log(dictionary1.getItemByIndex(0));
Note that sorting uses a quick sort algorithm by default but still has to iterate over the "indexes" map and update all the data, so a bit inefficient. If you can find a way to sort AND update the indexes at the same time, that would be better, but I cannot think of a way right at the moment.

Angular 4 Operator to add element in beginning of array and return array

Add the element to beginning of the array and returns the array as well. this is what i am looking for in typescript.I am using angular with redux and looking to write the reducer function which demands this solution. I cannot use unshift as it does not returns the arry and even tries splice(0,0,newObject) - doesn't work. Any other idea, help. concat does the work but adds to last of the array.
function Addrow(state:State,action:any):State{
return Object.assign({}, state,{
displayCodes: { list: copyobject.list.concat(state.displayCodes.list.length)},
filteredCodes: { list:copyobject.list.concat(state.displayCodes.list.length)}
});
There is no method that will add an element to the beginning of the array and return the array.
Using splice should work, I'd recommend trying it again:
function Addrow(state:State,action:any): State {
const length = state.displayCodes.list.length;
const list = copyobject.list.splice(0, 0, length);
return Object.assign({}, state, {
displayCodes: { list },
filteredCodes: { list }
});
}
You can also use unshift, just not as a one-liner.
function Addrow(state:State,action:any): State {
const length = state.displayCodes.list.length;
const list = copyobject.list;
list.unshift(length);
return Object.assign({}, state, {
displayCodes: { list },
filteredCodes: { list }
});
}

Categories