Making use of the key attribute in React JS - javascript

I have an array of values coming from an API, it contains the list of values. I am mapping all these values in an array of cards (as mentioned in the code below). I need to extract the id of the selected element, it is stored in the key attribute. Is there a way to use/reference the value of the key attribute?
{data.map((item) => (
<Card key={item[0]} element={item} />
))}
I am developing a KaiOS app and hence cursor interactions (onClick or onHover) are restricted. There is a nav-focus attribute (maintained using a custom hook) which is used to determine if the current element is selected and used CSS to highlight it (as shown in the image below) on that basis. I don't want to maintain a state of the selected element as it would be updated every time I navigate amongst the cards.
I would like to know if there is a way to use the key attribute of the selected item

I don't want to maintain a state of the selected element as it would be updated every time I navigate amongst the cards.
State is the correct way, why the want for not using state.
Keys should be used for uniquely identifying items rendered as a list, e.g. using map() and shouldn't really be used as other props would be.

Use a key in the html. It will solve the problem you suggested. and it will get the selected item as an attribute by the getAttribute property.
<div>
<select onChange = {this.onSelect}>
<option key="1" language="english">English</option>
<option key="2" language="espanol">Espanol</option>
</select>
</div>
Now onSelect function which will console log the selected language:
onSelect=(event)=> {
const selectedIndex = event.target.options.selectedIndex;
console.log(event.target.options[selectedIndex].getAttribute('language'));
}

Related

Displaying one value and sending another to backend with HTML <select> and <option> tags

I'm making a note-taking web app. The rich text editor includes a drop-down menu containing the names of each of the user's notebooks, so that the user can choose where the note will go. So far so good. The problem is that, in theory, the user could name two notebooks the same thing. So when the user chooses a notebook, the frontend ultimately needs to send the ID slug associated with that specific notebook to the backend, so that Django can choose which notebook to associate the note with without ambiguity.
The problem is that I don't know how to make the HTML <select> and <option> tags do what I want them to. I tried putting in one value between the HTML tags (so that it's displayed) and passing the ID as the tag's value attribute, but this doesn't seem to work:
setNotebooks(notebookList.map(item => {
return (
<option value={item.notebook_id}>
{item.name}
</option>
);
When I set notebooks in the way I showed above, it comes back as undefined. In the example, notebookList is an array of objects containing each notebook's attributes.
I tried wrapping each <option> tag in the <select> one in a higher-order component with its own state that would allow me to store the ID there, but this doesn't seem to work either.
How can I associate each plaintext name with its respective ID slug, so that the frontend will know which ID to pass to the backend, while still showing the user the plaintext name?
Im not expert in react but i think you need to set value="somevariable" attribute in select element which will hold current selected option value.
Turns out the react-select package had everything I needed. I can generate <option> tags based on objects I create, and when I try to assign values to state It Just Works™:
export function SelectNotebook({ currentNotebook, notebookOptions, setCurrentNotebook }) {
const customStyles = {
control: base => ({
...base,
width: 300,
minWidth: 300,
})
}
return (
<Select
options={notebookOptions}
value={currentNotebook}
onChange={option => setCurrentNotebook(option)}
placeholder='Select Notebook...'
styles={customStyles}
/>
);
}

Reactstrap - The return type of e when using Input of type "select"

When using the tag imported from reactstrap, I came across a minor problem.
I used
<.Input type="select" ... /.>
and wrapped inside it
<.option value={}....>
so that when an option was selected, the value of the input was set to the value of the selected option.
Even when the value of the Input tag was set with a number, accessing e.target.value provided me with a string, which I had to parse into an int.
So I was wondering if there is a specific return type for the Input tag of type select in reactstrap- even when the value is inputted as a number, does it automatically save it as a string?
It doesn't seem like there is a way to achieve this in reactstrap. However the beauty of React is that you are able to quickly write your own reusable component to achieve this.
const NumberSelect = (props) => (
<input type='select' onChange={(event) => {
props.onChange(parseInt(event.target.value, 10));
}}>
<option value={1}>One</option>
<option value={2}>Two</option>
</input>
);
You will need to tweak this code a bit according your own needs, but this is how I would solve your problem.

Changes to selected element in scope won't be persisted in array of available elements

I'm using angularJS 1.5.6 and try to update a value by using a combo box within an object that had already being selected through another combo box. But after switching the source object these changes are lost.
For a better understanding try this JSFiddle.
Within the above example the first combo box just selects an item out of a list of availables:
<select data-ng-model="currentElement"
data-ng-options="element.name for element in elements track by element.id"
data-ng-change="updateDeeperOption()">
</select>
The second combo box works on a second list of available options:
<select data-ng-model="deeperOption"
data-ng-options="option.name for option in availableOptions track by option.value"
data-ng-change="writeDeeperOptionToCurrentElement()">
</select>
The two method, which are being called whenever a change happens are simply updating the data in both directions:
$scope.updateDeeperOption = function() {
$scope.deeperOption = $scope.availableOptions.filter(function (option) {
return option.value === $scope.currentElement.going.deeper.to.myOption; })[0];
};
$scope.writeDeeperOptionToCurrentElement = function() {
$scope.currentElement.going.deeper.to.myOption = $scope.deeperOption.value;
};
The problem comes from writeDeeperOptionToCurrentElement(). It updates the current element (you can see it within the fiddle), but if you switch to another element and back again, the changes are lost.
Any idea, what I made wrong? I think, I'm still missing some fundamental understanding about when objects are copied or just referenced within AngularJS. So any explanation or links would be helpful.
Update:
I isolated the actual problem here and found that if you remove the track by from your first select then the currentElement object gets updated by reference. See: https://jsfiddle.net/tbzggyg1/4/
So instead of this:
<select data-ng-model="currentElement"
data-ng-options="element.name for element in elements track by element.id"
data-ng-change="updateDeeperOption()">
Try this:
<select data-ng-model="currentElement"
data-ng-options="element.name for element in elements"
data-ng-change="updateDeeperOption()">
And here is why: https://docs.angularjs.org/api/ng/directive/ngOptions
... is to use a track by clause, because then ngOptions will track the identity of the item not by reference, but by the result of the track by expression. For example, if your collection items have an id property, you would track by item.id.
Old answer:
It looks like your '$scope.currentElement' object is actually just getting a copy reference from the array, so when you make modifications it doesnt really update the original array (which is what your dropdowns bind to). I tried this and it worked:
$scope.writeDeeperOptionToCurrentElement = function() {
// $scope.currentElement.going.deeper.to.myOption = $scope.deeperOption.value; // old code
var elementRef = $scope.elements.filter(isMatchingElement)[0];
elementRef.going.deeper.to.myOption = "" + $scope.deeperOption.value;
};
function isMatchingElement(e) {
return e.id === $scope.currentElement.id;
}
Also, add this to your template so you can see the entire picture of whats happening:
<tt>{{currentElement}}</tt>
<hr>
<tt>{{elements}}</tt>
Link to fiddle forked: https://jsfiddle.net/t2dvm0a2/
In ng-options each option item is identified by the value you provide in track by.
Had you given your deep value in track by, things would have worked.
<select data-ng-model="currentElement"
data-ng-options="element.name for element in elements track by element.going.deeper.to.myOption"
data-ng-change="updateDeeperOption()">
</select>
Please check the updated fiddle
https://jsfiddle.net/tbzggyg1/3/

Remove blank option ng-repeat and ng-options after filter

There are several questions very similar to this one yet I have been unable to come up with a solution.
I have a select list using angularJS. I need to use the title attribute so I have an ng-repeat to create the options, there is always a blank option even if I use ng-selected to always select the first option.
Even if I make a selection and the blank option goes away, if I then filter out that selected value the blank will reappear.
I have included a select list using ng-option (which does not include my needed tittle attribute) and a default value to show that the blank will appear after filter.
The behavior I desire would be to never have a blank option (always selecting first option would be fine) and to possibly have a directive per option for special handling of click events.
Thanks in Advance!
JS Fiddle: http://jsfiddle.net/32DFM/3/
<select size="3" ng-model="person.current">
<option ng-repeat="p in people | filter:person.SearchTerm"
ng-selected="$first"
value="{{p}}"
title="{{p.name}}">
{{p.name}}
</option>
</select>
I forked your fiddle (if I may be so blunt): http://jsfiddle.net/XsFe8/2/
This fixes it somewhat. Although I haven't gotten it to work properly together with the filter.
Anyway, what I do here, is to use the person.id as the value on each option.
<select ng-model="person.current">
<option ng-repeat="p in people | filter:person.SearchTerm" ng-selected="$first" value="{{p.id}}" title="{{p.name}}">
{{p.name}}
</option>
</select>
And set the initial calue on the person.current model:
$scope.person.current = $scope.people[1].id;
But it's still not 100% though. I'm a bit stumped to why the blank spaces appear when you filter the select....
An alternative that might or might not work, would be to use something like ng-repeat="p in filterPeople() and filter your array in a filterPeople function. But I'm not sure if this will change anything.
UPDATE: I tested out my suggestion above, here: http://jsfiddle.net/XsFe8/2/
If you set the selected object to be the first object in the filtered array, it works. I do this each time a new filtered array is created:
$scope.filterPeople = function () {
var array = filterFilter($scope.people, $scope.person.SearchTerm);
$scope.person.current = array[0].id;
return array;
};
It looks like things get hairy when another object than what is visible in the select is actually selected. This is kind of understandable :)
Your actual problem is the value in ngModel is referencing a value which doesn't exist in the select anymore.
A solution is to whenever you alter the select options, you also check the person.current to ensure that it points to a valid entry.
This also implies that you might want to move your filter into the controller, and set the options in the scope (you can use the $filter service in your code to get same behaviour there, https://docs.angularjs.org/api/ng/filter/filter). This way you can have a function in your controller checking if person.current is valid, and if not, set it to desired options (e.g. the first one).
the hairyness cited above is due to an empty array when all items are filtered out and is fixed by:
if(array.length>0)
$scope.person.current = array[0].id;
http://jsfiddle.net/b0z6vpr8/
Hope this helps

Best way to store (and retrieve!) dom-element-specific data using javascript

I've got a matrix of drop down lists, and I'd like to be able to show the user when they have edited a setting visually (for example by setting the background of the table cell to red). If they switch it back to the original value, it will go back to the default background color. This way it will be unambiguously clear what the state of the system is.
To do this I have come up with setting an onchange handler on the <select> element like this: select.setAttribute('onchange','chSel(this,'+select.selectedIndex+');'); where select is a <select> element.
It will call the function chSel with a reference to itself and the current setting, so it will be able to set classes which will determine display properties via CSS:
function chSel(node, origSelIndex) {
if (node.selectedIndex == origSelIndex) // switching back to the set state
node.parentNode.removeAttribute('class');
else node.parentNode.setAttribute('class','cell_changed');
}
That doesn't require the retrieval of dynamic data, though the saved selectedIndex value in the onchange handler serves as element-specific static data.
I wanted to extend this functionality I have described by making a border around my table, which I intend to color red when ANY of its contents are modified, so that if a table is huge it is still possible to tell at a glance if any of its entries are modified.
There is a bad way and a better way to implement this. The bad way, which I do know how to implement in compliant HTML/XHTML, is to have chSel (which is executed any time the user interacts with a drop down menu) run a full search through the entire table to count edits, and mark the table border accordingly. This may not actually be a performance problem but I anticipate that it will be. I don't think it would scale that well.
The better way is to keep a count of the number of edited elements. When that number drops back to zero (user changes all his edits back to original values) I can re-mark the table as unedited.
However this requires that I have some method of determining if a select menu has been changed from a new value to a different new value, as opposed to changing from the original value to a new value, i.e. it is now relevant what the value being changed from is. There does not appear to be a way to do this. This discussion seems to indicate that I cannot create and retrieve custom attributes while keeping my document valid. When I have tried to assign arbitrary attributes I've had difficulty retrieving their values.
What's left? Can I build an object which I will use as a hash from elements to integers? Will javascript even let me do that?
I'm not sure I totally follow what you're trying to do, but you can keep track of state either in javascript variables or in custom DOM attributes. Javascript variables are slightly easier, but you have to give them an appropriate scope so they survive the duration of your operation. Custom DOM attributes are sometimes easier to make object oriented and to avoid any global javascript variables. jQuery's .data() capability is actually in between the two. It uses one custom data attribute to tag the DOM object with a unique tag and then stores all the actual data in a javascript map.
Once you store the state appropriately, anytime you get a change event, you can compare to the previous value and see what actually changed from the previous state, update your new state and save the new value as the previous value or do whatever else you need to do.
For example, in this way, you could keep a count of how many edited items there were and anytime that value goes from 0 to non-zero or non-zero to 0, you would change the overall table look as desired. Likewise, anytime an individual cell changes, you could update it's look.
If your DOM elements are your only data structure, then it's probably easiest to just create a custom attribute and keep additional data there. The HTML5 way of doing this is to preprend "data-" to your attribute names as in obj.setAttribute("data-origSelectIndex", x) or obj["data-origSelectIndex"] = x to keep them clear of any name conflicts with standard attributes.
If there's any reason you don't want to use custom attributes, then you would need to make a parallel javascript data structure (probably an array where each item in the array corresponds to one DOM element in some way that you can map between the two) that held the data.
Most form controls have a defaultValue property. To determine if any have changed from their default, cycle through them and compare the current value with the defaultValue. For select elements, always set one to selected, and check if the currently selected option has its defaultSelected property set to true. Similarly for radio buttons and checkboxes.
That way you don't have to store any values, it's done by the DOM elements themselves.
Working off of #jfriend00 's suggestion using dom attributes as a sort of global variable:
Jsfiddle demo.
CSS
.selected { border: 1px solid red;}
HTML
<div id="table1">
<select id="sel1">
<option>1</option>
<option>2</option>
<option selected>3</option>
</select>
<select id="sel2">
<option selected>1</option>
<option>2</option>
<option>3</option>
</select>
</div>
<div id="table2">
<select id="sel3">
<option>1</option>
<option selected>2</option>
<option>3</option>
</select>
<select id="sel4">
<option selected>1</option>
<option>2</option>
<option>3</option>
</select>
</div>
Javascript:
(function(){
var selects = document.getElementsByTagName('select'),
i = 0,
selectLength = selects.length;
var applyParentChange = function(node, val){
var currentEdits = node.getAttribute('data-edits') || 0;
currentEdits = parseFloat(currentEdits, 10) + val;
if(currentEdits <= 0) {
currentEdits = 0;
node.className = '';
} else {
node.className = 'selected';
}
node.setAttribute('data-edits',currentEdits);
};
var onSelectChange = function(){
var defaultIndex = parseFloat(this.getAttribute('data-default'),10);
if(defaultIndex === this.selectedIndex){
applyParentChange(this.parentNode, -1);
this.className = '';
} else {
applyParentChange(this.parentNode, 1);
this.className = 'selected';
}
};
for(i=0; i<selectLength; i+=1){
selects[i].setAttribute('data-default',selects[i].selectedIndex);
selects[i].onchange = onSelectChange;
}
})();
To walk through the code a bit...
You get a collection of all your select elements and give them a custom property with their default selected index and you give it a onchange event directly.
On change it checks the new index, versus the original (default) index and applies the CSS to the element if needed. Then it passes it off to the parent node (div in this case, but can be whatever).
In the parent we keep a similar custom attribute for the number of edits. The min of 0 is to keep it set if someone keeps re-selecting the default over and over. It applies the CSS if it has 1 or more edits, removing if it is 0.
Note that this is only one approach, you can keep a collection inside a variable that you keep references/counts in, some use hidden (offscreen) input elements that store values...

Categories