Why is my State object being Changed? React - javascript

I am trying to merge two array objects by the user_id key. The two array objects I get are from the state.
function mapStateToProps(state) {
let {users} = state.users.result;
let {invite} = state.invite.result;
let {friends} = state.friends.result;
if (users && invite && friends) {
let UsersList = mergeArray(users, invite.sent_invites); // Merge Users & Sent Invites
}
function mergeArray(a, b) {
return _.map(a, function(item) {
return _.assign(item, _.find(b, ['user_id', item.user_id]));
});
}
However, my {users} state also changes with the merge. Why is this? I have been stuck on this hours.

_.assign({}, item, _.find(b, ['user_id', item.user_id]));
_.assign assigns to the first argument, which in your cast was item, which was a reference to the object in state.users. So you make your first argument an empty object, so you are writing to a new object each time and won't modify any existing ones.

Related

Why does _.remove delete items from an original array?

I have a useState array that is being set with an array received from the backend. I am creating a new array equal to it and using _.remove to delete items from it. However, the original useState array is also having items deleted from it. I cannot figure out why this is the case
const [backendArray, setBackendArray] = useState([{some values}])
const newArray = backendArray;
useEffect(() => {
const arrayForPurpose1 = _.remove(newArray, function (n) {
return n.value != null;
}), [backendArray]};
Both arrayForPurpose1 and backendArray have item.values equalling null. Why is that the case? I created newArray thinking that would sever connection between the _.remove and backendArray but it didn't.

Function just re render one time

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.

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

How do I find objects with a property inside another object in JavaScript

I have a object with all my users, like so:var users = {user1:{}, user2:{}}, And every user has a isPlaying property. How do I get all users that have isPlaying false?
You should use Object.keys, Array.prototype.filter and Array.prototype.map:
// This will turn users object properties into a string array
// of user names
var userNames = Object.keys(users);
// #1 You need to filter which users aren't playing. So, you
// filter accessing users object by user name and you check that
// user.isPlaying is false
//
// #2 Using Array.prototype.map, you turn user names into user objects
// by projecting each user name into the user object!
var usersNotPlaying = userNames.filter(function(userName) {
return !users[userName].isPlaying;
}).map(function(userName) {
return users[userName];
});
If it would be done using ECMA-Script 6, you could do using arrow functions:
// Compact and nicer!
var usersNotPlaying = Object.keys(users)
.filter(userName => users[userName].isPlaying)
.map(userName => users[userName]);
Using Array.prototype.reduce
As #RobG has pointed out, you can also use Array.prototype.reduce.
While I don't want to overlap his new and own answer, I believe that reduce approach is more practical if it returns an array of user objects not playing.
Basically, if you return an object instead of an array, the issue is that another caller (i.e. a function which calls the one doing the so-called reduce) may need to call reduce again to perform a new operation, while an array is already prepared to fluently call other Array.prototype functions like map, filter, forEach...
The code would look this way:
// #1 We turn user properties into an array of property names
// #2 Then we call "reduce" on the user property name array. Reduce
// takes a callback that will be called for every array item and it receives
// the array reference given as second parameter of "reduce" after
// the callback.
// #3 If the user is not playing, we add the user object to the resulting array
// #4 Finally, "reduce" returns the array that was passed as second argument
// and contains user objects not playing ;)
var usersNotPlaying = Object.keys(users).reduce(function (result, userName) {
if (!users[userName].isPlaying)
result.push(users[userName]);
return result;
}, []); // <-- [] is the new array which will accumulate each user not playing
Clearly using Array.prototype.reduce concentrates both map and filter in a single loop and, in large array, reducing should outperform "filter+map" approach, because looping a large array twice once to filter users not playing and looping again to map them into objects again can be heavy...
Summary: I would still use filter+map over reduce when we talk about few items because sometimes readability/productivity is more important than optimization, and in our case, it seems like filter+map approach requires less explanations (self-documented code!) than reduce.
Anyway, readability/productivity is subjective to who does the actual coding...
Iterate through your users object:
var list = [];
for (var key in users) {
if (users[key].isPlaying === false) {
list.push(key);
}
}
This will give you a list of all users who have an isPlaying property that is false.
If you would like all of the user objects where isPlaying is false, you can add the objects themselves instead:
var list = [];
for (var key in users) {
if (users[key].isPlaying === false) {
list.push(users[key]);
}
}
This can also be achieved using Array.prototype.reduce, which is a great all round tool. It starts with getting an array of the names:
var userNames = Object.keys(users);
To return an array just the user names where isPlaying is false, you can do:
var usersNotPlaying = userNames.reduce(function(names, name) {
if (!users[name].isPlaying) {
names.push(name);
}
return names}, []);
To return an object of user objects with their names as keys is similar:
var usersNotPlaying = userNames.reduce(function(names, name) {
if (!users[name].isPlaying) {
names[name] = users[name];
}
return names}, {});
You could also use forEach in a similar way, however since it returns undefined the object or array collecting the members must be initialised in an outer scope first:
var usersNotPlaying = {};
userNames.forEach(function(name) {
if (!users[name].isPlaying) {
usersNotPlaying[name] = users[name];
}
});
You can also use for..in:
var usersNotPlaying = {};
for (var user in users) {
if (users.hasOwnProperty(user) && !users[user].isPlaying) {
usersNotPlaying[user] = users[user];
}
}
All of the above can return an array of names, array of user objects or object of user objects. Choose whatever suits. ;-)
Please try the JS code below: set all the isPlaying to false.
var json_object={};//{"user1":{"isPlaying":"false"},"user2":{"isPlaying":"ture"}};
json_object['user1']={"isPlaying":"false"};
json_object['user2']={"isPlaying":"ture"};
console.log(json_object);
for(var key in json_object){
if(json_object[key].isPlaying === "false"){/*do what you want*/}
}
console.log(json_object);

Categories