How to pass a value into a react button - reactjs

I have the following method as part of one of my components in a react app.
renderDeducs()
{
var deducArray=[];
for(var x =0;x<this.props.deducciones.length;x++)
{
var deduc = this.props.deducciones[x];
deducArray.push(<tr key={x}>
<td key={x.toString()+"nombre"}>{deduc.nombre}</td>
<td key={x.toString()+"porciento"}>{deduc.porciento}</td>
<td><button onClick={(e) => {this.delete(deduc.nombre)}}>Borrar</button> | <button>Editar</button> </td>
</tr>
);
}
console.log(deducArray);
return deducArray;
}
The idea is to be able to display the items of an array and provide a delete button for each item. The items are displayed inside an HTML table. However, the button for each row is deleting the last item in the array (the last row of the table). Apparently, even though "deduc" is a local variable and should be out of scope when the click events fire, deduc seems to have the value used in the last iteration of the loop. How can I tell each button what item of the array I want to delete?
EDIT:
here is the code for the "delete" method of the component and the constructor:
constructor(props)
{
super(props);
this.state = {
addMethod : this.props.addMethod,
delMethod : this.props.delMethod
};
this.renderDeducs = this.renderDeducs.bind(this);
this.delete = this.delete.bind(this);
}
delete(nombre)
{
alert("Deleting " + nombre);
this.state.delMethod(nombre);
}
As you can see, the actual delete from the array method happens on a method that is bound to a component higher up in the component tree (that how you you achieve the "single source of truth", isn't it?) On the parent component, this is what that method looks like:
delDeduction(nombre)
{
var stateArray = this.state.deducciones;
for (var x =0;x<stateArray.length;x++)
{
if(stateArray[x].nombre === nombre)
{
stateArray.splice(x,1);
this.setState({deducciones:stateArray});
return;
}
}
}

Change code as:
Your problem is, deduc is defined as var, so deduc is hoisted inside the function. Then try to define it with let, it will prevent this behaviour.
renderDeducs()
{
var deducArray=[];
for(var x =0;x<this.props.deducciones.length;x++)
{
let deduc = this.props.deducciones[x];
deducArray.push(<tr key={x}>
<td key={x.toString()+"nombre"}>{deduc.nombre}</td>
<td key={x.toString()+"porciento"}>{deduc.porciento}</td>
<td><button onClick={(e) => {this.delete(deduc.nombre)}}>Borrar</button> | <button>Editar</button> </td>
</tr>
);
}
console.log(deducArray);
return deducArray;
}
Info about javascript hoisting

You forgot to bind the handler.Also make sure to use the loop like below , it will make your life easy
renderDeducs() {
return (
{
this.props.deducciones.map(deduc =>
<tr>
<td key={x.toString()+"nombre"}>{deduc.nombre}</td>
<td key={x.toString()+"porciento"}>{deduc.porciento}</td>
<td>
<button onClick={this.deleteme.bind(this, deduc.nombre)}>
Borrar
</button> |
<button>Editar</button>
</td>
</tr>
)
}
);
}
deleteme(n){
let initial_val = this.props.deducciones.slice();
let obj_to_del= initial_val.find(d => d.nombre === n);
initial_val.remove(obj_to_del);
//update your state with the initial val
}

Related

Why everytime page react re-renders checkbox within map gets checked by default?

I have a table that gets populated by an ticketArray array of objects brought from an api.
Every object gets a final column of type checkbox
Every time I click on the checkbox I add that object ID to a "toDeleteRow" array.
And only when the Button Delete is clicked I loop through ticketArray comparing every item id against ids in toDeleteRow. If ids are equal I delete that object from ticketArray and I wipe out toDeleteRow
The problem is when the page gets re-render the first of the remaining rows gets checked by default (and internally gets added to the toDeleteRow WHICH I don't want to)
<tbody>
{ticketArray.map((ticket: any, index) => (
<tr key={index}>
<td>{ticket.Id}</td>
<td>{ticket.createdDate}</td>
<td>PR</td>
<td>
<i className="icon-edit icon-large" title="Edit" onClick={handlePopupModifShow} />
</td>
<td>
<input onChange={() => { AddToRemove(ticket.Id) }}
className="form-check-input" type="checkbox"></input>
</td>
</tr>
))}
</tbody>
This are my functions:
const AddToRemove= (id: string) => {
if (toDeleteRow.includes(id)) {
let val = toDeleteRow.indexOf(id, 0)
if (val> -1) { toDeleteRow.splice(val, 1); }
}
else { toDeleteRow.push(id) }
}
const Remove = () => {
if (toDeleteRow.length > 0) {
let newArray: SetStateAction<{ Id: string; createDate: string }[]> = []
for (let x = 0; x < toDeleteRow.length; x++) {
newArray = ticketArray.filter(function (obj) {
return obj.Id !== toDeleteRow[x];
});
}
setTicketArray(newArray)
toDeleteRow.splice(0, toDeleteRow.length)
}
else {
alert("Select at least one row")
}
}
Any suggestions? It's driving me nuts :S

document.queryselector returns null and I am unsure why

This is in the front end of a react/js app. In my file called supplier container the following is part of my component Did Mount function:
componentDidMount() {
var element = document.querySelector('.supplier-data');
}
Then in the render method I call another file which calls another file which calls another file which has the following in its render method:
<tbody>
{this.props.data.map((dataItem, data_k) => (
<tr key={data_k} className={dataItem.deleted_at ? 'deleted-supplier' : null}>
{this.props.config.map((configItem, conf_k) => (
<td key={` ${conf_k}-${data_k} `} className="align-middle supplier-data">
{this.getValue(dataItem, configItem)}
</td>
))}
</tr>
))}
</tbody>
Note the className = supplier-data in the td tag. I am trying to access the value from the getValue function which is as follows:
getValue(dataItem, configItem, defaultVal = 'N/A') {
var value = null;
if (configItem.content) {
var value = this._getValueFromMethod(dataItem, configItem);
}
if (configItem.attribute && configItem.attribute.search(/\./) !== -1) {
var value = this._getValueFromSegmentAttribute(dataItem, configItem)
}
if (configItem.attribute && configItem.attribute.search(/\./) == -1) {
var value = this._getValueFromStaticAttribute(dataItem, configItem);
}
if (configItem.dateFormat && value) {
value = moment(value).format(configItem.dateFormat);
}
return value || defaultVal;
}
The other functions in this are not important. When I console log element from componentDidMount above I get null. How do I solve this?
Instead of using a querySelector to get the element, you should use React's ref

How to use ng-click value as ng-repeat property?

How to get $scope.clickedVal in r.clickedVal?
Button click calls a function, passing in two parameters:
<a ng-click="display('Info', 'Admission_Info'); filters.Admission_Info= '!!'; ">Info</a>
<a ng-click="display('Info', 'SomeOther_Info'); filters.SomeOther_Info= '!!'; ">Other</a>
Function in the controller:
$scope.display = function (col, val) {
$scope.filters = {};
$scope.clickedColumn = col;
$scope.clickedVal = val;
};
Table displays a filtered view with 2 columns.
<tr ng-repeat="r in response | filter:filters as filtered " ng-show="filtered.length">
<td>{{ r.Name }}</td>
<td>{{ r.clickedVal }}</td>
</tr>
{{ r.{{clickedVal}} }} didn't work.
So for example, if the first button is clicked, r.clickedVal should return r.SomeOther_Info.
I've tried creating a filter as well but run into the same issue - m.value is incorrect.
SchoolProgramsApp.filter('myFilter', function () {
return function (input, column, value) {
var out = [];
angular.forEach(input, function (m) {
if (m.value === value) {
out.push(m)
}
})
return out;
}
});
You should use bracket notation to access property of the object by variable name:
{{ r[clickedVal] }}

Mutliplr print functionality return inconsistent data , on changing $timeout value

I want to print a multiple barcode slip, each will have different barcode.
Using a print service to print the div content,
(function() {
'use strict';
angular.module('app.services')
.factory('PrintService', PrintService);
PrintService.$inject = [];
function PrintService() {
var service = {
printElement: printElement
};
return service;
function printElement(elem) {
var printSection = document.getElementById('printSection');
// if there is no printing section, create one
if (!printSection) {
printSection = document.createElement('div');
printSection.id = 'printSection';
document.body.appendChild(printSection);
}
var elemToPrint = document.getElementById(elem);
// clones the element you want to print
var domClone = elemToPrint.cloneNode(true);
printSection.innerHTML = '';
printSection.appendChild(domClone);
window.print();
window.onafterprint = function() {
printSection.innerHTML = '';
}
};
}
})();
Using this print service, will print the slip. Slip data will bind.
var userServicePromise = UserService.printBarCodes(sampleId);
userServicePromise.then(function(response) {
if (response != null && response.data != null && response.data.result != null) {
response.data.result.forEach(function(entry) {
/* $timeout(function() {
vm.barCodeImage = angular.copy(entry);
}, 0);*/
//vm.testName = item.testMast.testName.slice(0, 3);
vm.barCodeImage = angular.copy(entry);
$timeout(function() {
PrintService.printElement("printThisElement");
}, 1);
});
} else {
toaster.error(response.data.message);
}
});
This is the html which will be printed eventually, using DOM element id for printing.
<div id="printThisElement" class="onlyprint" >
<table>
<tr>
<td>{{ ctrl.instCode }}</td>
<td align="center">{{ ctrl.date | dateDisplayFilter}} </td>
</tr>
<tr>
<td colspan="2" align="center"> <img ng-src="data:image/JPEG;base64,{{ctrl.barCodeImage}}"> </td>
</tr>
<tr>
<td colspan="2" align="center">{{ ctrl.user.name }} </td>
</tr>
<tr>
<td >Reg Id: {{ ctrl.regIdLookup }}</td>
<td align="center">{{ ctrl.testName }}</td>
</tr>
</table>
</div>
Expected out put is three slips with different barcode:
7865
7866
7867
Output is three slips with same barcode
7865
7865
7865
some times,
7866
7866
7866
On changing the $timeout(function() value output be like
7865
7865
7866
what can be the reason for this ?
Never ever modify the DOM from inside a service, that's just totally against the whole way Angular works. What you should do instead is create a model of the data (and it's quite alright to create that in the service) and use Angular's templates to render that model in the page.
The reason your code doesn't work is that you are trying to re-use vm.barCodeImage for different images on the page. Angular tracks the changes and will redraw existing parts of the page. That is why you get the same barcode repeated: the different copies each use the same model so they will be the same.
A simple solution is to create an array ofvm.barCodeImages and then just render them in an ng-repeat loop. A better way might be to create a myBarcode directive which uses UserService.printBarCodes to create one barcode in an isolated scope and then your template will look shorter and tidier.

Refreshing ng-repeat without $scope, without new request to sever

I have a table with this content:
<tbody>
<tr ng-repeat="result in ctrl.result track by $index">
<td ng-bind="$index+1"></td>
<td ng-bind="::result.title"></td>
<td> <button type="button" class="btn" ng-click='ctrl.deleteItem(result)'>Delete</button></td>
</tr>
</tbody>
And in my controller I have:
vm.deleteItem = function (result) {
myService.deleteItem(result.id)
.then(function (response) {
vm.result.splice(result, 1);
});
};
As you see, the vm.result has changed if the item be deleted successfully.
Now, the item deleted in db, so we have response and then the item has been removed from vm.result, too. But the list hasn't updated in browser.
As you see, I use controller as approach and not $scope .
Try deleting the item this way:
vm.result.splice(vm.result.indexOf(result), 1);
The first Splice parameter should be the index of element rather than element itself.
var index = vm.result.findIndex(function(item) {
if (item.id == test.id) {
return true;
}
});
if (index !== -1) {
// Removing the test from list;
vm.result.splice(index, 1);
}
check based on id from the array test.id is the id of the element u r deleting

Resources