Why doesn't React use the object reference as key? - javascript

When rendering a list of items, React uses the index of each item as a default key, if none is supplied.
return <div>
{this.props.shows.map(show => <ShowComp title = {show.title}/>)}
</div>;
But why doesn't React use the JS object reference of each item instead? Seems to me like a much safer option, as it would not cause re-rendering of any of the items if the list order changes (as opposed to the index approach).
I'm relatively new to React, so I'm sure there's an underlying reason.
In Angular, ngFor (which renders a list of elements, similar to mapping object to React elements), it also has a trackBy configuration option (which is the equivalent of key in React). However, if it's not supplied, it uses the object references as an identifier, which seems more natural.

The reason why it's not using the reference of the object by default is all React's developpers choice.
Keep in mind it doesn't have to be unique across all the items, it has to be unique across the siblings of each item.
As shown here : https://reactjs.org/docs/reconciliation.html#keys
You could definitely use a not so unique key that's only unique across siblings.
Also, is using the reference of the object faster than most small integer found in unique ids ?

Related

Observe change of object proprty in nested array in Ember.js

We have this data structure
[[{foo:"12", bar:"34"}, {foo:"33", bar:"55"}], [{foo:"45", bar:"67"}]]
How is it possible to make a computed property to observe a change on foo or bar?
Normally you use .#each.someProperty in a computed property to watch specific object properties in an array but in the case of nested arrays (or nested objects), you can't do .#each.#each.someProperty because .#each only works one level deep.
You need to use intermediary computed properties to raise the inner objects up one (or more) levels and have another computed property watch these in turn. There may be other ways to wire things up, but this is a pretty straightforward way to do it.
Here's a twiddle that demonstrates it. If you open the console you can see the individual computed properties recording a change occurring.
You can also use an alias to pull the inner arrays / objects up a level or two.

Accessing specific array element in an Angular2 Template

I have an array that I can loop through using ng-for syntax. However, ultimately I want to access just a single element of that array. I cannot figure out how to do that.
In my component script I have
export class TableComponent {
elements: IElement[];
}
In my template, I am able to loop through the elements via
<ul>
<li *ngFor='let element of elements'>{{element.name}}</li>
</ul>
However, trying to access an item in the element array by secifically referencing an item utilizing
x {{elements[0].name}}x
does not seem to work.
The formatting in the template is pretty explicit, so I want to be able to access each element of the array explicitly in the template.
I am not understanding something basic....
2020 Edit :
{{elements?.[0].name}}
is the new way for the null check
Original answer :
{{elements[0].name}}
should just work. If you load elements async (from a server or similar) then Angular fails when it tries to update the binding before the response from the server arrived (which is usually the case). You should get an error message in the browser console though.
Try instead
{{elements && elements[0].name}}
Work around, use ngIf check the length. elements? means if elements is null, don't read the length property.
<div *ngIf="elements?.length">
{{elements[0].name}}
</div>

What should I use as unique identifier for an element of a mapped array?

I have read somewhere that one should use this
{someArray.map(function (element) {
return (
<span key={element.id}>{element.name}</span>
);
})}
instead of this
{someArray.map(function (element, key) {
return (
<span key={key}>{element.name}</span>
);
})}
Apparently, the reason is that key doesn't necessarily preserve the order of the array elements because it is not "tied" to the array element.
As a consequence, apparently, React would need to do more work than when using the element id element.id and potentially screw up the order of the rendered elements.
The docs seem to be in line with the above:
In practice, finding a key is not really hard. Most of the time, the
element you are going to display already has a unique id.
As someone who uses key all the time I am after some evidence that I shouldn't.
Does anyone have an example? Or has anyone benchmarked the two blocks to see if they differ performance-wise?
did some research second approach is good When React reconciles the keyed children, it will ensure that any child with key will be reordered (instead of clobbered) or destroyed (instead of reused).
Keys should be stable, predictable, and unique. Unstable keys (like those produced by Math.random()) will cause many nodes to be unnecessarily re-created, which can cause performance degradation and lost state in child components.

Why can I not map keys onto React component

my render looks like this:
<ul>{liElements}</ul>
the liElements is an array of <li>word</li> created with a for-of loop.
(for the sake of argument, I do not have access to the function that creates that array)
I'm understanding the value of keys, however they are not valuable in this situation as none of the component's values will be changing, Especially since in absence of a key, "react takes the array position as key" anyway, Perfect! I want that, why does it insist on sending me a error message letting me know they are necessary, I know it doesn't know they're not necessary, be it also doesn't know they are necessary, so how bout a yellow warning?
I know I can ignore it, but it is very annoying to always have the message log to console, (especially since this message could useful elsewhere)
react.js:19287 Warning: Each child in an array or iterator should have a unique "key" prop
So since I can do
liElements[0].key // null
I thought I can just do
liElements.map(function(a,i){a.key=i;});
<ul>{liElements}</ul>
alas;
bundle.js:39123 Uncaught TypeError: Cannot assign to read only property 'key' of object '#<Object>'
Why is it readOnly? Is there anyway I can assign these totally unnecessary keys after I receive the array just so the error message stops showing?
Lets say you have a list of items you want to be li's
let items = ['item 1', 'item 2', 'item 3', 'item 4', 'item 5']
let elems = items.map( (item, index) => { return <li key={index}>{item}</li> }
You want to map your list and as you do specify a key. Why do this? because you are rendering a tree into the DOM and for React to know which exact element you are going to try and manipulate you need to pass a specific key. This way you can do things like onClick handlers.. and stuff like that.
Additionallly React uses these specific keys for performance in rendering. so even if you think its unnecessary and the end result is identical when not passing a key. It still is REALLY important, because React wont have to generate a key for every item every single render.
Finally when you go to render it.
return (
<ul>
{elems}
</ul>
)
Edit
To what Dave was saying in the comments.. Keys are created on object creation aka React.createElement(...). After their creation they are immutable. Again this is because you are creating a tree.. if you try to manually change a key then the whole tree structure would have to be 're-keyd' which is a bad idea (this is why it is read only). The key is an internal thing that react uses, but when you render dynamic elements you need to specify the key so there is no duplication of keys.
Edit 2
Heres some helpful links for you
Another Answer I Gave Regarding Keys
React Dynamic Children Documentation
Importance of Keys - Example
Dynamic elements with examples of wrong ways to do it
If you're trying to set keys, I believe you have to set them in the li elements themselves like this:
<li key={index}>list item</li>

Keeping insertion order in Angular

I have a HTML fragment that iterates over key, value collection. When I create an object and put some value in, then iterate trough that object via HTML fragment, all works perfectly.
However since I need keys in specific order, I'm using a Map instead of plain object. This time when I debug I can see that my insertion order was preserved, but for some reason the HTML fragment which iterates over collection doesn't seem to know how to do so. I see nothing on my screen when I use the map object, opposed to the regular object when I see unordered content
tr ng-repeat="(key, value) in rowTitlesValues"
Is how my HTML fragment looks like, when I switch rowTitlesValues back to object works again, what am I doing wrong, and how does one keep insertion order or how do I sort object so it's keys are in custom order?
From Angular reference on ng-repeat (link):
Iterating over object properties
It is possible to get ngRepeat to iterate over the properties of an object using the following syntax:
<div ng-repeat="(key, value) in myObj"> ... </div>
You need to be aware that the JavaScript specification does not define the order of keys returned for an object. (To mitigate this in Angular 1.3 the ngRepeat directive used to sort the keys alphabetically.)
Version 1.4 removed the alphabetic sorting. We now rely on the order returned by the browser when running for key in myObj. It seems that browsers generally follow the strategy of providing keys in the order in which they were defined, [...]
If this is not desired, the recommended workaround is to convert your object into an array that is sorted into the order that you prefer before providing it to ngRepeat. You could do this with a filter such as toArrayFilter or implement a $watch on the object yourself.
Additionally, I do not think Angular 1.x knows how to iterate over a Map. I believe this line in the code proves it:
collectionKeys = [];
for (var itemKey in collection) { // iterates your object using `in`, not `of` or `Map.forEach()`
...
}
// ng-repeat then iterates the collectionKeys to create the DOM
So you will probably need to act as Angular docs suggest:
[...] convert your object into an array that is sorted into the order that you prefer before providing it to ngRepeat. You could do this with a filter such as toArrayFilter or implement a $watch on the object yourself.

Categories