I've recently started with Reactjs and I find it really interesting. Keys are about identity, so identifying each component through a unique key is the way to go, right?
Suppose I have this example:
var fruits = [
{'fruitId': 351421, 'fruit': 'banana'},
{'fruitId': 254134, 'fruit': 'apple'},
{'fruitId': 821553, 'fruit': 'orange'}
];
React.DOM.ul(null, fruits.map(function(item) {
return (
React.DOM.li({
key: item.fruitId
}, item.fruit)
);
}));
Note the big IDs numbers. Now, my question is if is better to use numbers as IDs or strings like a hashes as IDs?
Thanks!!
It really doesn't matter, the only thing that matters is that the key is unique among siblings within the parent element. It doesn't have to be unique across your entire app, just inside the parent you're appending these items to.
Often for simple iteration over elements, such as <li> or <option> it's fine to just use the index within your iterator.
EG:
var options = [];
for (var i=0; i<this.props.options.length; i++) {
var option = this.props.options[i];
options.push(
<option key={i} value={option.value}>{option.name}</option>
);
}
The only time this doesn't work well is if you are adding/removing keyed elements in different orders etc later on so that your key might clash with another sibling. In which case you're going to want to generate the key in some other way to make sure it's unique - or use a known unique key from your model. Whichever way you do it as long as it's unique among it's siblings, it'll be fine.
As #Mike says, they are only used to preserve the ordering of the real DOM elements if your are adding to/removing from a list. They only need to be unique to the local component, so it's ok to reuse natural ids from your data. I would not use an index from an iterator because of this.
Re. numbers, vs strings: if you're concerned about performance, I'd use whatever type you've already got. Any conversion/parsing you do will be done on every render. However, this would be pretty low on my list of performance concerns in my app.
Related
I have a use case where I want to assign unique ids to each element of array while iterating over it. Note, This array contains duplicate values. I want to use index as a key in react element, but linter rules do not allow me to use index as a key.
I thought of using System tics. Is there a way I can get system ticks so that on every instruction execution, I get a new value?
If yes, what is the recommended precision?
I would suggest to use neither indices nor system ticks as both options take you away from the functionality that the key is supposed to provide.
In the documentation it says: "Keys help React identify which items have changed, are added, or are removed. Keys should be given to the elements inside the array to give the elements a stable identity"
So there are a variety of options:
You can create a function to generate unique keys/ids/numbers/strings and use that
You can make use of existing npm packages like uuid, uniqid, etc
You can also generate random number like new Date().getTime(); and prefix it with something from the item you're iterating to guarantee its uniqueness
Lastly, you can use the unique ID you get from the database, if you get it (hard to say without seeing your data)
However: Keys should be stable, predictable, and unique. Unstable keys (like those produced by Math.random()) will cause many component instances and DOM nodes to be unnecessarily recreated, which can cause performance degradation and lost state in child components.
You can read more here: https://reactjs.org/docs/reconciliation.html#keys
So there is no perfect answer for that. It depends on your use case...
Even though #Archer 's approach might already be feasible enough, I provide this solution that I mostly use in cases like the ones described by the OP ...
var createId = (
/* [https://github.com/broofa/node-uuid] - Robert Kieffer */
(window.uuid && (typeof window.uuid.v4 === 'function') && window.uuid.v4)
/* [https://gist.github.com/jed/982883] - Jed Schmidt */
|| function b(a){return a?(a^Math.random()*16>>a/4).toString(16):([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g,b)}
);
console.log('createId() : ', createId());
console.log('createId() : ', createId());
console.log('createId() : ', createId());
.as-console-wrapper { max-height: 100%!important; top: 0; }
How it is
I have an array of objects called vm.queued_messages (vm is set to this in my controller), and vm.queued_messages is used in ng-repeat to display a list of div's.
When I make an API call which changes the underlying model in the database, I have the API call return a fresh list of queued messages, and in my controller I set the variable vm.queued_messages to that new value, that fresh list of queued messages.
vm.queued_messages = data; // data is the full list of new message objects
The problem
This "full replacement" of vm.queued_messages worked exactly as I wanted, at first. But what I didn't think about was the fact that even objects in that list which had no changes to any properties were leaving and new objects were taking their place. This made no different to the display because the new objects had identical keys and values, they were technically different objects, and thus the div's were secretly leaving and entering every time. THIS MEANS THERE ARE MANY UNWANTED .ng-enter's AND .ng-leave's OCCURRING, which came to my attention when I tried to apply an animation to these div's when they entered or left. I would expect a single div to do the .ng-leave animation on some click, but suddenly a bunch of them did!
My solution attempt
I made a function softRefreshObjectList which updates the keys and values (as well as any entirely new objects, or now absent objects) of an existing list to match those of a new list, WITHOUT REPLACING THE OBJECTS, AS TO MAINTAIN THEIR IDENTITY. I matched objects between the new list and old list by their _id field.
softRefreshObjectList: function(oldObjs, newObjs) {
var resultingObjList = [];
var oldObjsIdMap = {};
_.each(oldObjs, function(obj) {
oldObjsIdMap[obj._id] = obj;
});
_.each(newObjs, function(newObj) {
var correspondingOldObj = oldObjsIdMap[newObj._id];
if (correspondingOldObj) {
// clear out the old obj and put in the keys/values from the new obj
for (var key in correspondingOldObj) delete correspondingOldObj[key];
for (var key in newObj) correspondingOldObj[key] = newObj[key];
resultingObjList.push(correspondingOldObj);
} else {
resultingObjList.push(newObj);
};
});
return resultingObjList;
}
which works for certain things, but with other ng-repeat lists I get odd behavior, I believe because of the delete's and values of the objects being references to other controller variables. Before continuing down this rabbit hole, I want to make this post in case I'm thinking about this wrong, or there's something I'm missing.
My question
Is there a more appropriate way to handle this case, which would either make it easier to handle, or bypass my issue altogether?
Perhaps a way to signal to Angular that these objects are identified by their _id instead of their reference, so that it doesn't make them leave and enter as long as the _id doesn't change.
Or perhaps a better softRefreshObjectList function which iterates through the objects differently, if there's something fishy about how I'm doing it.
Thanks to Petr's comment, I now know about track by for ng-repeat. It's where you can specify a field in your elements that "identifies" that element, so that angular can know when that element really is leaving or entering. In my case, that field was _id, and adding track by message._id to my ng-repeat (ng-repeat="message in ctrl.queued_messages track by message._id") solved my issue perfectly.
Docs here. Search for track by.
Say you have a very simple data structure:
(personId, name)
...and you want to store a number of these in a javascript variable. As I see it you have three options:
// a single object
var people = {
1 : 'Joe',
3 : 'Sam',
8 : 'Eve'
};
// or, an array of objects
var people = [
{ id: 1, name: 'Joe'},
{ id: 3, name: 'Sam'},
{ id: 8, name: 'Eve'}
];
// or, a combination of the two
var people = {
1 : { id: 1, name: 'Joe'},
3 : { id: 3, name: 'Sam'},
8 : { id: 8, name: 'Eve'}
};
The second or third option is obviously the way to go if you have (or expect that you might have) more than one "value" part to store (eg, adding in their age or something), so, for the sake of argument, let's assume that there's never ever going to be any more data values needed in this structure. Which one do you choose and why?
Edit: The example now shows the most common situation: non-sequential ids.
Each solution has its use cases.
I think the first solution is good if you're trying to define a one-to-one relationship (such as a simple mapping), especially if you need to use the key as a lookup key.
The second solution feels the most robust to me in general, and I'd probably use it if I didn't need a fast lookup key:
It's self-describing, so you don't
have to depend on anyone using
people to know that the key is the id of the user.
Each object comes self-contained,
which is better for passing the data
elsewhere - instead of two parameters
(id and name) you just pass around
people.
This is a rare problem, but sometimes
the key values may not be valid to
use as keys. For example, I once
wanted to map string conversions
(e.g., ":" to ">"), but since ":"
isn't a valid variable name I had to
use the second method.
It's easily extensible, in case
somewhere along the line you need to
add more data to some (or all) users.
(Sorry, I know about your "for
argument's sake" but this is an
important aspect.)
The third would be good if you need fast lookup time + some of the advantages listed above (passing the data around, self-describing). However, if you don't need the fast lookup time, it's a lot more cumbersome. Also, either way, you run the risk of error if the id in the object somehow varies from the id in people.
Actually, there is a fourth option:
var people = ['Joe', 'Sam', 'Eve'];
since your values happen to be consecutive. (Of course, you'll have to add/subtract one --- or just put undefined as the first element).
Personally, I'd go with your (1) or (3), because those will be the quickest to look up someone by ID (O logn at worst). If you have to find id 3 in (2), you either can look it up by index (in which case my (4) is ok) or you have to search — O(n).
Clarification: I say O(logn) is the worst it could be because, AFAIK, and implementation could decide to use a balanced tree instead of a hash table. A hash table would be O(1), assuming minimal collisions.
Edit from nickf: I've since changed the example in the OP, so this answer may not make as much sense any more. Apologies.
Post-edit
Ok, post-edit, I'd pick option (3). It is extensible (easy to add new attributes), features fast lookups, and can be iterated as well. It also allows you to go from entry back to ID, should you need to.
Option (1) would be useful if (a) you need to save memory; (b) you never need to go from object back to id; (c) you will never extend the data stored (e.g., you can't add the person's last name)
Option (2) is good if you (a) need to preserve ordering; (b) need to iterate all elements; (c) do not need to look up elements by id, unless it is sorted by id (you can do a binary search in O(logn). Note, of course, if you need to keep it sorted then you'll pay a cost on insert.
Assuming the data will never change, the first (single object) option is the best.
The simplicity of the structure means it's the quickest to parse, and in the case of small, seldom (or never) changing data sets such as this one, I can only imagine that it will be frequently executed - in which case minimal overhead is the way to go.
I created a little library to manage key value pairs.
https://github.com/scaraveos/keyval.js#readme
It uses
an object to store the keys, which allows for fast delete and value retrieval
operations and
a linked list to allow for really fast value iteration
Hope it helps :)
The third option is the best for any forward-looking application. You will probably wish to add more fields to your person record, so the first option is unsuitable. Also, it is very likely that you will have a large number of persons to store, and will want to look up records quickly - thus dumping them into a simple array (as is done in option #2) is not a good idea either.
The third pattern gives you the option to use any string as an ID, have complex Person structures and get and set person records in a constant time. It's definitely the way to go.
One thing that option #3 lacks is a stable deterministic ordering (which is the upside of option #2). If you need this, I would recommend keeping an ordered array of person IDs as a separate structure for when you need to list persons in order. The advantage would be that you can keep multiple such arrays, for different orderings of the same data set.
Given your constraint that you will only ever have name as the value, I would pick the first option. It's the cleanest, has the least overhead and the fastest look up.
I started off with an array of objects and used _.filter to filter down on some search criteria and _.findWhere to select out asingle object based on ID.
Unfortunately the amount of data has increased so much so that it's much more efficient to use _.indexBy to index by ID so I can just do data[ID] = id for the _.findWhere's.
However I am stumped on how to replace the _.filter method without looping through all the keys in data.
Is there a better way?!
Edit
The IDs are always unique.
I can't show any real data as it is sensitive but the structure is
data = {
1: {id: 1, name: 'data1', date: 20/1/2016}
2: {id: 2, name: 'data2', date: 21/1/2016},
3: {....
}
and I need to something like:
var recentData = _.filter(data, function(d){d.date > 1/1/2016});
To get an array of data or ids.
(n.b. the dates are all in epoch times)
This is really an optimization question, rather than simply which function to use.
One thing to go about this would be if we could rely on sort order of the whole collection. If it's already sorted, you go with something like binary search to find the border elements of your date range and then splice everything from this point. (side note: array would probably work better for this).
If the array is not sorted you could also consider sorting it first on your end - but that makes sense only if you need to retrieve such information several times from the same dataset.
But if all you got is just the data, unsorted and you need to pick all elements starting from a certain date - no other way that iterate through it all with something like _.filter().
Alternatively you could revert back to the source of your data and check whether you can improve the results that way - if you're using some kind of API, maybe there are extra params for sorting or narrowing down the date selection (generally speaking database engines are really efficient at what you're trying to do)? Or if you're using a huge static JSON as the source - maybe consider improving that source object with sort order?
Just some ideas. Hard to give you the best resolution without knowing all the story behind what you're trying to do.
As part of a Chrome extension I am searching the entire DOM for elements containing specific words inside each ID/Class.
Currently it looks something like:
"allSelectors": document.querySelectorAll("[id*='example'][class*='example']"),
"collapse": function () {
for (var i = 0; i < MyObject.allSelectors.length; i++) {
// Manipulate MyObject.allSelectors[i] etc
}
},
First, I would like to restructure it somehow (possibly using an array?) so that it is easy to add new selectors as doing it like this:
document.querySelectorAll("[id*='example'][class*='example'][id*='other'][class*='other']")
Isn't easily scaleable or good.
Secondly, I think document.querySelectorAll is very slow - the reason I am using it is because I need to search anywhere in an id/class (hence the use of *=) and cannot use a big external library (such as jQuery), as this is a small file and is being injected user side.
Is there an any solution to either of these problems? because if there are many matches then this slowness might become an issue.
First of all I would totally go for querySelectorAll, I don't think it's that slow, and also it totally fits in a situation like yours. I agree with you that adding a library is overkill for this, and additionally it might not be as beneficial as someone thinks (let's test it here).
Then, again I agree with you that the current solution is not very scalable and that the array is the way to go. Here's a very basic implementation using an array:
// an array of classes and ids to match
var nodes,
searches = [
'[id*="test"]',
'[class*="example"]'
];
// a simple function to return an array of nodes
// that match the content of the array
function getNodes(arr){
return Array.prototype.slice.call(document.querySelectorAll( arr.join() ));
}
nodes = getNodes(searches);
The good thing is that new classes and ids can be easily added or removed from the array, for example, later on you can add:
searches.push('[id*="some"]');
nodes = getNodes(searches); // new nodes will be fetched
Here's a jsbin with a full example code.