I have started to learn angular recently. I came across a code as follows.
Consider this.members as an array of objects. There is an object which has the username bob. I am trying to get that particular object using the below code.
const data = this.members.find(x=>x.userName===username);
So there an object inside the array and I have an object stored in my const data, My doubt is that will both (the object in the array and the object in the const data ) have the same memory address. If someone could answer why changing the const data is also getting reflected in the this.members array. It would be a great help. You can also share some resources if I need to go through them to understand them better.
To shortly answer your question, yes. The data object will have a reference to the object inside this.members.
If you want to prevent that, there are multiple ways I'm sure, but one of them is to use Object.assign.
Example:
let data: any = {};
Object.assign(data, this.members.find(u => u.username === 'bob'));
console.log(this.members); // For example: [{username: 'bob'},{username: 'randy'}]
data.username = 'alex';
console.log(this.members); // Still shows [{username: 'bob'},{username: 'randy'}]
console.log(data); // {username: 'alex'}
Notice here that I am using TypeScript since you mentioned you're working with Angular.
When using Object.assign, a copy of the enumerable properties will be made and assigned to your variable without referencing the source.
See MDN Docs for more details.
Another simple way is to use the spread operator.
let data: any = {};
const foundUser = this.members.find(u => u.username === 'bob');
if (foundUser) {
data = {...foundUser};
}
This will create a new object with the properties from the foundUser.
You can easily try it out.
const members = [
{ username: 'bob' },
{ username: 'daniel' },
];
const data = members.find(x=>x.username==='bob');
data.username = 'bobby';
console.log(members);
That prints
[ {
"username": "bobby" }, {
"username": "daniel" } ]
So yes, changing data will change the array.
The answer for this question is that Arrays in javascript are mutable which means they are reference type which means when I encounter the below line
const data = this.members.find(x=>x.userName===username);
const data will have the same memory location as that of bob in the array. As we all know that if we change data at a memory location every variable/object referring to that memory location will also change as they all point to the same memory location. Hence the array gets updated even though I assign a part of the array and make changes to that part alone.
Note: The above was my expected behavior. if you want the opposite behavior like if you need the const data and this.members to be independent you can use
Copy Array
or you can refer to Maher's answer in the same page.
Related
I have the function below that is used to dynamically update a list of objects when the textarea is typed into:
const [questions, setQuestions] = useState([]);
//array of these objects
//0: {
//Category: "Some_ID"
//Description: "Some Description"
//Question: "Some Question.."
//}
function EditQuestion(e) {
const updatedQuestions = questions;
updatedQuestions[e.target.dataset.index][e.target.name] = e.target.value;
setQuestions([...updatedQuestions]);
}
//somewhere in the return..
<textarea
className="textarea"
value="Description"
data-index="1"
name="Description"
onChange={EditQuestion}
/>
My question is what is the spread operator doing here that makes this work:
setQuestions([...updatedQuestions]);
and the reason these don't work, or give unwanted results when setting the state?
setQuestions([updatedQuestions]); or
setQuestions(updatedQuestions); ?
Well imagine that updateQuestions is just a [{}, {}] (array of objects) and when you just put setQuestions([updatedQuestions]) you pretty much doing this setQuestions([ [{}, {}] ]) (Array of array of objects)
The first case:
setQuestions([updatedQuestions]);
This case will not work because you have to pass an array of items, not an array of array.
The second case:
setQuestions(updatedQuestions);
This case will not work, because of this line :
const updatedQuestions = questions;
You need to use immutable data because you just passed the same value(reference), and React will think that its the same value and will not rerender the component, it can work if you updated your code like this :
const updatedQuestions = {...questions}; //this line will create new array instead of pointing to old one.
NOTE: This doesn't exactly answer your question about the spread operator, but it does something that I think is more important for you.
This pattern is everywhere and it's so wrong:
function EditQuestion(e) {
const updatedQuestions = questions; <- this line is irrelevant, confusing, and a huge bug trap
...
}
It's irrelevant because you now have two variables - updatedQuestions and questions which both access the exact same object in memory, but a lot of people seem to think that they're different. See half the answers on SO about "my component didn't rerender after I updated my state"
To answer a part of your question, this won't work:
function EditQuestion(e) {
const updatedQuestions = questions;
updatedQuestions[e.target.dataset.index][e.target.name] = e.target.value;
setQuestions(updatedQuestions);
}
Because, as I just pointed out, questions === updatedQuestions - you mutated state. Rule #1 in react - don't mutate state.
A better EditQuestion implementation looks like this:
function EditQuestion(e) {
setQuestions((questions) => {
// questions is now a variable containing your existing questions state
// map returns a "NEW" array - therefore this isn't mutating state
return questions.map((q,i) => {
// this is not the question we're interested in, so just return q
if(i != e.target.dataset.index) return q;
// this is the question we're interested in, update it
// be sure to return a new object and not mutate the old one
return {...q, [e.target.name]: e.target.value }
});
});
}
The questions variable is an array that should only be modified by using the setQuestions function. The parameter you give to setQuestions should be the new array. Let's explore why the following don't work:
setQuestions([updatedQuestions])
Here you're passing an array of an array. That's not what questions is supposed to be. It will not crash, but you end up with a nested array which isn't what you expected.
setQuestions(updatedQuestions);
This won't work because the object you're sending is really the original questions object since both variables (questions and updatedQuestions) just point to the same array. Since it's not changing the setQuestions won't really change the value.
I think you intended to build a copy of the questions and add to it, so this code would make more sense:
function EditQuestion(e) {
const updatedQuestions = [...questions]; // copies to a new array
updatedQuestions[e.target.dataset.index][e.target.name] = e.target.value;
setQuestions(updatedQuestions); // replace the original with the new array
}
Regarding the spread operator, you can use that to get a shallow copy of the array. For example:
const x = [{ a: 1 }, { b: 2 }];
const y = [ ...x ];
// changing y doesn't change x
y.push({ c: 3 });
console.log('x:', x);
console.log('y:', y);
// but both point to the same objects within
y[1].b = 22;
console.log(`x[1].b ${x[1].b} y[1].b: ${y[1].b}`)
// you might expect x[1].b to still be 2 but it's not
So in your example, the spread operator helps you build a shallow copy of the questions array which means setQuestions sees a new array coming in and replaces the old with the new.
ReactJS re-renders whenever there is a change in state or props. In your case to see if the state has changed it does shallow caparison of previous & new state props. Here you have a non-primitive data structure where state questions are actually storing the reference to the value stored in memory.
const questions = [{ questionName: 'old question' }];
const updatedQuestions = questions;
updatedQuestions[0].questionName = "edited question"; // mutating the nested value;
questions === updatedQuestions; // this will print true, since they still refer to the same reference
const destructuredQuestions = [...updatedQuestions] // also this only works for 1st layer, deep nested are still connected
questions === destructuredQuestions // this will print false, on restructuring it start storing the reference to new memory
From experience, I highly recommend restructuring nested things before mutating/update the values.
I'm new to JavaScript and I want a Data Structure for my JavaScript code which stores Student data as key value pairs. The key is the student Registration number and the value is the students name.What I'm thinking is to create a JavaScript object as follows and store data as follows
let Student={
001A:"John",
002A:"Mathew"
};
Is this approach is correct? And if it is correct suppose a way to dynamically add key value pairs to that. Thank you
That would be an object literal. You'd want the key to be a string, but other than that you've basically got it. You can dynamically add properties using bracket syntax. I've attached a small snippet to give you an idea of how it works. :)
let Student={
"001A":"John",
"002A":"Mathew"
};
Student["003A"] = 'Jessica';
Object.entries(Student).forEach(entry => console.log(entry) );
The approach is correct. Given
const students={
'001A': 'John',
'002A': 'Mathew',
};
(Note: It's a good idea to keep your key as a string to prevent collisions with reserved keywords)
To read from the structure you can access the given record via
console.log(students['001A']); // => 'John'
To add new records dynamically, you just add a new property and assign it the desired value:
students['007'] = 'Ben';
which results in
console.log(students);
// =>
{
'001A': 'John',
'002A': 'Mathew',
'007': 'Ben',
};
To delete a given record you can do either
delete students['007'];
or
students['007'] = undefined;
The first option is a little "cleaner" as it completely deletes the given key, along with its assigned data.
Keep in mind that the data will be removed once you reload the page.
Using const token = response.json().token; I am able to get the following JSON:
{
"token": "*********",
"roles": [
{
"id": 4,
"name": "User",
"pivot": {
"user_id": 1,
"role_id": 4
}
}
]
}
I want to be able to access the names within the roles as an array.
If you just want to access the array of roles, assuming you're using a traditional response object, you can just access it the way other users have stated:
var roles = response.json().roles
As I reread the question and comments, I get the idea that the user wants to access the names within the roles as as list. Assuming so, the map function will do this nicely:
// Assuming we store response in "data"
var data = response.json();
var names = data.roles.map(function(role){return role.name});
console.log(names);
// Then "names" will look like ["User",...]
In a nutshell, map will walk the array it's called against and run the provided function against it, with the first argument being the current value it sees in the array. This function can be defined on the fly as above, or predefined and passed if the logic is complex or reused.
This is a very common use for Map, and its sibling, Reduce. Both are often used for distilling complex, variable-length data down to a simpler form.
Map documentiation:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map
Update: It would appear the original question was asked regarding ES6, so here's the "proper" ES6 version with proper declarations and arrow funcs:
// Assuming we store response in "data"
const data = response.json();
const names = data.roles.map(role => role.name);
console.log(names);
// Then "names" will look like ["User",...]
I'm still learning JS. In some other languages, you can pass variables byref and then modify them elsewhere in code.
In an attempt to avoid having lots of duplicate code, I have structured a series of callbacks and parsing like so:
class MarketData {
constructor() {
//Arrays
this.OneMinuteData = [];
this.ThreeMinuteData = [];
this.initializeCandleData();
}
initializeData() {
var client = new Client();
this._initializeData(60, client, this.OneMinuteData);
this._initializeData(180, client, this.ThreeMinuteData);
}
_initializeData(granularity, client, dataStore) {
client.GetRates({ granularity: granularity }, function(err, msg, data) {
var items = data.map(item => ({
///data mapped here
}));
dataStore = dataStore.concat(items);
}
}
So essentially I have this 'private' _initializeData function with the hopes of passing in an array and having it add to the array, but since JS passes byval, I cannot achieve the desired effect (e.g. this.OneMinuteData array is not modified).
Because of this, the only way I currently know how to work around this problem is to essentially have the same function copy-pasted for each individual array, which I find incredibly sloppy. Is there a better way of doing this?
but since JS passes byval, I cannot achieve the desired effect (e.g. this.OneMinuteData array is not modified).
While JavaScript does pass by value, that value when dealing with an object (including any array) is a reference.
See the documentation for concat:
The concat() method is used to merge two or more arrays. This method does not change the existing arrays, but instead returns a new array.
So when you say dataStore = dataStore.concat(items);, you assign a new array to the local dataStore variable and discard the old one.
Outside the function, the original array is unchanged.
The reason the array assigned to OneMinuteData is not modified is because you never modify any array.
Push the values of items into dataStore instead.
dataStore.push.apply(dataStore, items);
NB: GetRates has the signature of an asynchronous function, so make sure you don't try to inspect the modifications to OneMinuteData before they are made.
I'm trying to get a handle on using .map, .filter to clone and modify a big nested JSON object based on a deeply nested property. With the below code, the original data and the filtered data both end up getting modified, but I'm trying to leave the original data intact. What I'm hoping to do is have the deeply nested concerns array emptied in the final filtered object for a given id, leaving the original data as the original complete data set.
var data {...};
var dataFilter = function dataBuild(data) {
var newData = data;
newData.service_requests = newData.service_requests.map((request) => {
request.concerns = request.concerns.filter((concern) => {
return concern.building_id == 2
});
return request;
});
return newData;
};
var filtered = dataFilter(data);
Here's a fiddle with what I'm trying to do with the full object in there.
http://jsbin.com/doyoqinoxo/edit?js,console
When you do:
var newData = data;
you are simply making a second reference to the same object, so:
newData.service_requests = ...
overwrites the value in data.service_requests.
It seems you want newData to be a copy of data, not a reference to the same object. There are plenty of posts here on how to copy a nested object (a so–called deep copy), e.g. What is the most efficient way to clone an object?, but please ignore the accepted answer unless you are using jQuery. Use one of the other answers, like this one.
JSIterator .map() creates the new array with the same number of elements or does not change the original array. There might be the problem with referencing if there is object inside the array as it copies the same reference, so, when you are making any changes on the property of the object it will change the original value of the element which holds the same reference.
The solution would be to copy the object, well, array.Splice() and [...array](spread Operator) would not help in this case, you can use JavaScript Utility library like Loadash or just use below mention code:
const newList = JSON.parse(JSON.stringify(orinalArr))