Can one add React Keys after instantiation? - javascript

I’m making a collection of React Elements and displaying them; what follows is a trivial example to frame the problem of how-would-one-modify-an-preexisting-instantiated-element only.
var c = [
<div>A</div>,
<div>B</div>,
// ...
<div>Z</div>
];
var ListComponents = React.createClass({
render: function() {
return <div>{c}</div>;
}
});
ReactDOM.render(<ListComponents/>, document.getElementById('root'));
While the code above “works,” it renders a console message I’d rather not ignore:
Warning: Each child in an array or iterator should have a unique "key" prop.
Check the render method of `ListComponents`.
See https://fb.me/react-warning-keys for more information.
Superficially, I could just add a unique key="…" string to each element in c and be done with it.
However, that seems a quite verbose, especially since I have the data in an indexed array and a functional language that in theory can assign each key its matching index value without manually having to enter it as a source literal.
I’d love to be able to just do this...
c.forEach( (e,i) => e.key = i ); // ...or call some setter
What’s the *right* React-way to do this -and- keep the code clean?
ADDENDUM:
...for the curious or those that want to just say add a key field...
The collection I'm using is actually an array of tuples containing meta-data and a corresponding React Element, a custom Component, or some huge JSX block. The example above overly trivializes what the actual data looks like as well as its irregularities.
As the source data itself is quite long, updated often, and not maintained by a developer; it is highly error prone to missed key fields or duplicates values from manual entry. Hence the desire to do it entirely programmatically. I can not count on the data owners to do it properly. They can't read code, so ideally I'd rather not mess up the data structures with a lot of "programming goop."
The collection is manipulated a few times, putting various runs of certain elements into other dynamically created wrappers, so that the final collection is actually generated by a few transformations, filters, and maps before it is ultimately displayed.

A major shout out to Wes Bos, who came up with a clever solution that works!
The code is a simple one liner and does exactly what I was looking for:
c = c.map( (el,key) => React.cloneElement(el, {key} ));
We're building a new collection using the .cloneElement() method, which I was unaware of. That was what I needed, it turns out.
In the .map() operation, the lambda function is passed both the element and the index. It's return value is a cloned element, but with the key property set.
By cleverly naming the index element key, it allows the short notation for the expression { "key" : key }. This object augments the cloned object.
In the end, I end up with a new collection of identical objects, each with a key property set to the index.

Related

Angular: How to map an array of 'mat-checkbox's to get strings associated with the checked checkboxes

To preface, I have found a solution that works for me in the situations I have tried it, but I am fairly new to javascript and RXJS and don't fully understand what the solution is doing so I can't be sure if it will work in every instance.
I am using reactive forms and have arrays of checkboxes, what I would like to do is get an array of all of the keys used to generate the checkboxes, but without any of the unchecked boxes. Ideally, I could use this to return additional values as well such as a user-readable string, but that is far from important as I can do that in a separate step fairly easily. I have come up with a few methods of my own, but they don't seem to be very robust or performant.
I would have replied on this thread, but I lack the reputation.
This is the best solution I have found, which I would be totally happy to use, but I don't have much experience with RXJS or maps in javascript so I am not totally sure what it is doing:
this.controlNames = Object.keys(this.checkboxGroup.controls).map(_=>_); //This seems to just produce an object of control keys as properties and their value
this.selectedNames = this.checkboxGroup.valueChanges.pipe(map(v => Object.keys(v).filter(k => v[k]))); //Some sort of magic happens and an array is generated and contains only the keys whose values are 'true'
I have tried breaking that snippet apart and using console.log to test what it is doing in each step, but it really didn't give me much useful information. Any advice or or better ideas would be thoroughly appreciated, there seem to be a lot of conventions in javascript that people adhere to and it can be hard to sort through what is a convention and what is actually doing something.
I think I found a way to break it down and get a grip on it and want to post my explanation for anyone who comes looking.
In this part, it is just creating an iterable map of the 'checkboxGroup.controls' object. This could have been used to loop over in the template and make all of the checkboxes. Since my form structure is already generated from arrays of objects with known properties, I don't need this. The underscores aren't doing anything special here, people just like to use them for private variables.
this.controlNames = Object.keys(this.checkboxGroup.controls).map(_=>_);
For those who are new to arrow functions or some of the conventions of javascript, the code above is not quite, but essentially shorthand for this:
this.controlNames = [];
Object.keys(this.checkboxGroup.controls).forEach(function(key) {
this.controlNames.push(key);
}
I have changed the short variables to longer ones to make them easier to understand in this second part. This maps the value changes observable as an iterable 'changesObj', retrieves the keys, and filters the keys by instances where the key has a true value. The code filter(key => changesObj[key]) returns the key if the key is not null, undefined, or false.
this.selectedNames = this.checkboxGroup.valueChanges.pipe(map(changesObj => Object.keys(changesObj).filter(key => changesObj[key])));
This is essentially just shorthand for this:
function propNotFalse (changes, prop) {
return changes[prop] == true;
}
this.selectedNames = this.alternateFilter = Object.keys(this.checkboxGroup.valueChanges).filter(this.propNotFalse.bind(null, this.checkboxGroup.valueChanges));

How do I iterate through objects stored in a firebase array in JavaScript?

At the moment I am storing a few objects in Firebase. After successfully retrieving the items from Firebase and storing them in a firebaseArray, I want to further thin out the unwanted elements by deleting the elements in the firebaseArray that do not have the desired property. Consider my code at the moment, that does not do as wanted, however there are no errors in the console:
var querylatestPosts = firebase.database().ref("Topics");
$scope.latestPosts = $firebaseArray(querylatestPosts);
console.log($scope.latestPosts) ;
$scope.latestPosts.forEach(function(el) {
if ($scope.checkWorldview(el) == false) {
delete $scope.latestPosts.el ;
}
});
(Note I am unable to log 'el' in the console, nor does the forEach seem to execute, as I can log nothing in the function in the console)
The 'checkWorldview' function behaves as expected when elements are fed in different instances and returns false if the required property is not present in the element under consideration. Thus if the function returns false, I want to delete the specific element in $scope.latestPosts that does not contain the wanted property.
I hope this is clear, thank you in advance for any help you can offer!
The way you are using the $firebaseArray isn't recommended by the docs (see here), which state that $firebaseArray is read only and should not be manipulated.
So you have a few options:
Instead of filtering the array on the client-side, you should modify the query you're using to retrieve data from Firebase to only get elements that have the desired property (ex: use 'equalTo' in the query)
OR
Don't use a $firebaseArray because you're not using it in the way it was intended. Use a regular, good ol' fashion JavaScript array instead.
** Also, just a general comment: don't delete elements from an array as you loop through it as this is generally bad practice (we don't expect arrays to have elements added/removed while we loop through them). Instead, use Array.filter.

React list with no keys

I have an array of number that I wish to render in a tabular form. The array is returned from an API call, not generated by my app.
The data may change but is unlikely to do so, and in any case there are only twenty odd values, so re-rendering the whole table is not really a problem.
A simple data.map(value => <td>{value}</td> should do it.
But I keep getting an Each child in an array or iterator should have a unique "key" prop. warning. Is there any way that I can tell React that there is no key and that I wish it to re-render the whole table if anything changes.
Alternatively, is there any way that I can generate a unique key for each entry? The data items are not guaranteed to be unique.
I should add that I understand what keys are for and why they are useful, but in this instance I do not have any and the easiest thing would be not to use them, since there is unlikely to be a re-render.
You can use the index as the key. I think its worth reiterating that using the index as the key only works fine in the very specific scenario that the OP is facing.
This is particularly annoying when requirements change and all of sudden the list is being modified. This shows up as items not being updated during the render because the item updated has the same key (the index), its value is different, but react only cares about the key.
In cases where your data has no unique key. You should use some function that generates a unique id for each item. A simple version of that function just increments a global counter:
// Declared globally (as in attached to window object or equivalent)
var myuniqueidcounter = 0;
function uniqueId() {
myuniqueidcounter += 1
return myuniqueidcounter;
}
// Do this in the props change or whereever your data gets passed in
let keyedData = data.map(value => Object.assign(value, { Id: uniqueId() });
// In render
data.map(value => <td key={value.Id}>{value}</td>
That way, on multiple render calls, the ids returned are always unique. We assign the key when we get the data to avoid having to re-render the entire list on each call to render().
However, this case is actually pretty rare as you can usually find some combination of the backing data that will produce a unique key for each entry.
If you do go index-as-key
This article lists 3 conditions that should be met when choosing index-as-key approach that I think is a good check list:
The list and items are static–they are not computed and do not change;
The items in the list have no ids;
The list is never reordered or filtered.
data.map((value,index) =>{
<td key={index}>{value}</td>}
)
or
data.map((value,index) =>{
let i = Math.floor(Math.random() * 1000+1)
<td key={i}>{value}</td>}
)
You can use index as your key as it is unique each time
Based on the question asked, it might be worth saying that there is an another solution to this that doesn't use keys:
e.g. The following will complain about not having unique keys:
React.createElement('div', {}, [<span>1</span>, <span>2</span>]);
However, the following renders all children with no problems (This is what JSX transformed to JS looks like for nodes with multiple children):
React.createElement('div', {}, <span>1</span>, <span>2</span>);
So if you have e.g. a smallish list of generated react element fragments and unique keys don't offer and advantage in your situation, you can do:
React.createElement.apply(null, ['div', {}, ...elementList])
Notes:
elementList is passed as arguments to React.createElement which might be an issue if the list is huge.
It will re-render all the children with each render.
Using unique keys is generally the recommended approach, and is more performant for re-rendering.
However there are occasions where you just want to render in a single shot and don't care about re-rendering, or the data is not structured in a way that you can make good use of unique keys. You can use this as a work-around if you really need to.

Avoiding duplication of key/data

I have a design annoyance with some existing code in JS. The code is working, so I have no desperate hurry to change it, but the duplication shown below does annoy me. What is the usual/recommended/official way of avoiding this situation?
The actual system is a large/complex financial system, so I have simplified it to the most basic example which demonstrates the problem:
var colours={
red:{id:"red", vals:[1,0,0]},
green:{id:"green", vals:[0,1,0]},
grey:{id:"grey", vals:[0.5,0.5,0.5]}
// ...etc
};
// id needs to be known internally within the object - thus it is defined as a property.
// e.g:
colour.prototype.identify(console.log(this.id));
// id also needs to be used externally to find an object quickly.
// e.g:
function getcolour(s){return colours[s];}
// Although this works. It does mean duplicating data, with the theoretical possibility of a mismatch:
var colours={//...
blue:{id:"green", // oh dear...
How would this normally be handled by the experts?
This question is somewhat subjective.
When creating my applications I typically try do do the following:
never define same data in multiple places. source should always be unambiguous
if I need to create any indices for faster/easier access, I use utility methods to do it. Those methods should be properly unit-tested, so that I would have little doubts on them doing the wrong thing
use third party libraries as much as possible (such as already suggested lodash or underscore) to minimize the amount of code to be written/maintained.
If your algorithms and utilities are properly unit-tested you should not worry (too much) about getting the data into inconsistent state. However, if those are critically important systems/interfaces, you may add some validation on output. And it is generally a good practice to have data validation and marshaling on input.
Explanation on the utility methods:
if you have data array, say
var data = [{"id":"i_1", ...}, {"id":"i_2", ...},{"id":"i_3",....}];
Then and you have to create an index out of that or create more data sets based on the original array, then you create yourself a library of utility methods that do the modification on the array, create derivative data sets, or iterate on the array and create a resulting item on the fly. For example:
var createIndex = function( arr ){
// do something that converts the data array with expected structure to object
// {
// i_1: {"id":"i_1", ...},
// i_2: {"id":"i_2", ...},
// i_3: {"id":"i_3", ...}
return newObj;
}
This method will create a hash-map to access your data, which is faster then to iterate over the original array all the time. But now, this method you can easily unit-test and be sure that when you use it on the source data to get your intended dataset, there will be no inconsistency.
I wouldn't change the colours[key] direct access with other method to avoid duplication.
Any other attempt will lead to processing and you have mentioned that you have a large amount of data.
I assume that the duplication is over the incoming data that is a waste.
An example of processing over the network data consuming could be, going over the map object and set the id dynamically according to the key. (processing vs traffic)
colours[key].id = key
You can filter your object converting it to an array of objects and then filtering unique values. Converting it to an array would allow you to perform a lot of operations quicker and easier.
So you can map your object to an array:
var coloursArray = myObj.map(function(value, index) {
return [value];
});
Remove duplicates:
function removeDuplicates() {
return coloursArray.filter((obj, pos, arr) => {
return arr.map(mapObj => mapObj[id]).indexOf(obj[id]) === pos;
});
}
You can remove duplicates from an array using for example underscore.js through the .uniq method:
var uniqueColoursArray = _.uniq(coloursArray , function(c){ return c.id; });
Moreover, this function is pretty useless because you can access your element directly:
function getcolour(s){return colours[s];}
Calling colours[s] it is also shorter than getcolour(s). Your function would make sense if you pass also the array because it is not accessible in some other scope.
Then I can't understand why you do pass a console.log as parameter here:
colour.prototype.identify(console.log(this.id));
maybe you would like to pass just the this.id

What is the accepted convention for when to use an object containing objects vs an array of objects in JSON?

I am currently in the process of writing a GUI which fundamentally allows users to edit/populate/delete a number of settings files, where the settings are stored in JSON, using AJAX.
I have limited experience with JavaScript (I have little experience with anything beyond MATLAB to be frank), however I find myself restructuring my settings structure because of the semantics of working with an object containing more objects, rather than an array of objects. In C# I would do this using a KeyValuePair, however the JSON structure prevents me from doing what I'd really like to do here, and I was wondering whether there was an accepted convention for do this in JavaScript which I should adopt now, rather than making these changes and finding that I cause more issues than I solve.
The sample data structure, which has similar requirements to many of my structures, accepts any number of years, and within these any number of events, and within these a set number of values.
Here is the previous structure:
{"2013":
{
"someEventName":
{
"data1":"foo",
"data2":"bar",
...},
...},
...}
Here is my ideal structure, where the year/event name operates as a key of type string for a value of type Array:
["2013":
[
"someEventName":
{
"data1":"foo",
"data2":"bar",
...},
...],
...]
As far as I am aware, this would be invalid JSON notation, so here is my proposed structure:
[{"Key":"2013",
"Value":
[{"Key":"someEventName",
"Value":
{
"data1":"foo",
"data2":"bar",
...}
},
...]
},
...]
My proposed "test" for whether something should be an object containing objects or an array of objects is "does my sub-structure take a fixed, known number of objects?" If yes, design as object containing objects; if no, design as array of objects.
I am required to filter through this structure frequently to find data/values, and I don't envisage ever exploiting the index functionality that using an array brings, however pushing and removing data from an array is much more flexible than to an object and it feels like using an object containing objects deviates from the class model of OOP; on the other hand, the methods for finding my data by "Key" all seem simpler if it is an object containing objects, and I don't envisage myself using Prototype methods on these objects anyway so who cares about breaking OOP.
Response 1
In the previous structure to add a year, for example, the code would be OBJ["2014"]={}; in the new structure it would be OBJ.push({"Key":"2014", "Value":{}}); both of these solutions are similarly lacking in their complexity.
Deleting is similarly trivial in both cases.
However, if I want to manipulate the value of an event, say, using a function, if I pass a pointer to that object to the function and try to superceed the whole object in the reference, it won't work: I am forced to copy the original event (using jQuery or worse) and reinsert it at the parent level. With a "Value" attribute, I can overwrite the whole value element however I like, provided I pass the entire {"Key":"", "Value":""} object to the function. It's an awful lot cleaner in this situation for me to use the array of objects method.
I am also basing this change to arrays on the wealth of other responses on stackoverflow which encourage the use of them instead of objects.
If all you're going to do is iterate over your objects, then an array of objects makes more sense. If these are settings and people are going to need to look up a specific one then the original object notation is better. the original allows people write code like
var foo = settings['2013'][someEventName].data1
whereas getting that data out of the array of objects would requires iterating through them to find the one with the key: 2013 which depending on the length of the list will cause performance issues.
Pushing new data to the object is as simple as
settings['2014'] = {...}
and deleting data from an object is also simple
delete settings['2014']

Categories

Resources