last row is being deleted instead of clicked row in ReactJS - reactjs

I want to delete a row from html table on button click using ReactJS. The problem is that on clicking delete button always last row is being deleted instead of the row that is clicked. Please tell what is the issue with my code?
code:
var RecordsComponent = React.createClass({
getInitialState: function() {
return {
rows: ['row1', 'row2', 'row3'],
newValue: "new value"
}
},
render : function() {
return (
<div>
<table>
<tbody>
{this.state.rows.map((r) => (
<tr>
<td>{r}</td>
<td>
<button onClick={this.deleteRow}>Delete</button>
</td>
</tr>
))}
</tbody>
</table>
<input trype="text" id={"newVal"} onChange={this.updateNewValue}></input>
<button id="addBtn" onClick={this.addRow}>ADD</button>
</div>
);
},
updateNewValue: function(component) {
this.setState({
newValue: component.target.value
});
},
addRow : function() {
var rows = this.state.rows
rows.push(this.state.newValue)
this.setState({rows: rows})
},
deleteRow : function(record) {
var index = -1;
var clength = this.state.rows.length
for( var i = 0; i < clength; i++ ) {
if( this.state.rows[i].value === record.target.value ) {
index = i;
break;
}
}
var rows = this.state.rows
rows.splice( index, 1 )
this.setState( {rows: rows} );
}
});
React.render(<RecordsComponent/>, document.getElementById('display'))

You need pass a param on func deleteRow
<button onClick={() => this.deleteRow(r)}>Delete</button>
I refactor a litte bit of your func
deleteRow : function(record) {
this.setState({
rows: this.state.rows.filter(r => r !== record)
});
}

In deleteRow you are looking for the index of the row that matches record.target.value (record is actually an event), but event.target.value is blank so the index remains -1, and rows.splice(-1, 1) deletes the last element.
You should pass the row data itself, like:
<button onClick={e => this.deleteRow(r)}>Delete</button>

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

knockout - header checkbox stays unchecked in table header when clicked

I am new to knockout and I am stuck at a problem for last couple of days - I am sure it is something silly but cant figure out. Any help will be appreciate.
I am trying to select and deselect all rows in a table based on the header check box column. The SelectAll function works and selects/unselects all rows in table but the header remains unckecked?
<tr>
<th><input type="checkbox" data-bind="click: selectAll, checked: AllChecked"></th>
<th>#Html.Vocab("Document")</th>
<th>#Html.Vocab("Notes")</th>
<th>#Html.Vocab("Created")</th>
</tr>
<tbody data-bind="foreach: DocumentRows">
<tr >
<td><input type="checkbox" data-bind="checked: IsSelected"></td>
<td><data-bind="text: Notes"></td>
</tr>
</tbody>
And here is the script:
//Document
class Document {
Id: KnockoutObservable<number>;
Notes: KnockoutObservable<string>;
IsSelected: KnockoutObservable<boolean>;
constructor(data?) {
this.Id = ko.observable(0);
this.Notes = ko.observable("").extend({ defaultValue: "" });
this.IsSelected = ko.observable(false);
if (data != null) {
ko.mapping.fromJS(data, {}, this);
}
}
};
//DocumentS VIEW MODEL
class DocumentsViewModel {
DocumentRows: KnockoutObservableArray<Document>;
IsAnySelected: KnockoutObservable<boolean>;//used for delete button
constructor(params) {
this.DocumentRows = ko.observableArray([]);
this.selectedIds = ko.observableArray([]);
}
InitComputed = () => {
this.AllChecked= ko.pureComputed({
read: function () {
return this.selectedIds().length === this.DocumentRows().length;
},
write: function (value) {
this.selectedIds(value ? this.DocumentRows.slice(0) : []);
},
owner: this
}
this.IsAnySelected = ko.pureComputed(() => {
var isChecked = false;
ko.utils.arrayForEach(this.DocumentRows(), function (item) {
if (item.IsSelected()) {
isChecked = true;
}
});
return isChecked;
});
}
selectAll = (): void => {
if (this.selectedIds().length > 0) {
this.selectedIds.removeAll();
ko.utils.arrayForEach(this.DocumentRows(), function (item) {
item.IsSelected(false);
});
} else {
ko.utils.arrayPushAll(this.selectedIds(), this.DocumentRows())
ko.utils.arrayForEach(this.DocumentRows(), function (item) {
item.IsSelected(true);
});
}
}
}

Scroll through table rows programmatically

The objective of this plunk is to have a table where the up and down keys will be used to select rows programmatically and scroll through the table. The selected row will have a different background color.
When keying up/down I use e.preventDefault() to avoid the rows to move up/down twice. Problem is that when I start scrolling down the rows stay fixed and the selected row disappears. How to fix this?
HTML
<div id="selector" tabindex="0" ng-keydown="scroll($event)"
style="width:300px;height:80px;border:1px solid gray;overflow-y:auto">
<table>
<tr ng-repeat="item in items">
<td class="td1" ng-class="{'tdactive' : $index==index }">{{item.col}}</td>
<td class="td1" ng-class="{'tdactive' : $index==index }">{{item.dsc}}</td>
</tr>
</table>
</div>
Javascript
var app = angular.module('app', []);
app.controller('ctl', function($scope) {
document.getElementById("selector").focus();
$scope.items = [ {col:"aaa", dsc:"AAA1"}, {col:"bbb", dsc:"BBB2"} , {col:"ccc", dsc:"CCC3"},
{col:"aaa2", dsc:"AAA21"}, {col:"bbb2", dsc:"BBB22"} , {col:"ccc2", dsc:"CCC23"},
{col:"aaa2", dsc:"AAA21"}, {col:"bbb2", dsc:"BBB22"} , {col:"ccc2", dsc:"CCC23"} ];
$scope.index = 0;
$scope.scroll = function(e) {
if (e.which === 40) { // down arrow
if ($scope.index<$scope.items.length - 1)
$scope.index++;
e.preventDefault();
}
else if (e.which === 38) { // up arrow
if ($scope.index>0)
$scope.index--;
e.preventDefault();
}
};
});
First of all you need to add table row id as id="tr-{{$index}}"
You can then prevent your scroll if tr is in the current viewport
$scope.scroll = function(e) {
var parentContainer = document.getElementById("selector");
if (e.which === 40) { // down arrow
if ($scope.index<$scope.items.length - 1)
{
var element = document.getElementById("tr-"+$scope.index);
if(isElementInViewport(parentContainer,element)){
e.preventDefault();
}
$scope.index++;
}
}
else if (e.which === 38) { // up arrow
if ($scope.index>0)
{
var element = document.getElementById("tr-"+$scope.index);
if(!isElementInViewport(parentContainer,element)){
e.preventDefault();
}
$scope.index--;
}
}
};
function isElementInViewport(parent, el) {
if(parent==undefined || el==undefined)
return false;
var elRect = el.getBoundingClientRect(),
parRect = parent.getBoundingClientRect();
//console.log(elRect)
//console.log(parRect)
var elementHeight = elRect.height;
return (
elRect.top >= parRect.top &&
elRect.bottom <= parRect.bottom &&
elRect.bottom+elementHeight<= parRect.bottom
);
}
Working Plunker

ReactCSSTransition on table rows and empty table state

I'm looking to implement a table in ReactJS with the following features:
initially empty
rows are dynamically added and removed
when there are no rows, an empty state (e.g. a box saying "Table empty") should be displayed
when a row is removed, there should be a fade out transition
when the first row is added, there should be no fade out transition on the empty state
I came up with two approaches using ReactCSSTransitionGroup.
1. Wrap only rows into ReactCSSTransitionGroup
Codepen: https://codepen.io/skyshell/pen/OpVwYK
Here, the table body is rendered in:
renderTBodyContent: function() {
var items = this.state.items;
if (items.length === 0) {
return (
<tbody><tr><td colSpan="2">TABLE EMPTY</td></tr></tbody>
);
}
const rows = this.state.items.map(function(name) {
return (
<tr key={name}>
<td>{name[0]}</td>
<td>{name[1]}</td>
</tr>
);
});
return (
<ReactCSSTransitionGroup
component="tbody"
transitionName="example"
transitionEnter={false}
transitionLeave={true}>
{rows}
</ReactCSSTransitionGroup>
);}
The issue is that the last row to be removed does not get the fade out transition before disappearing since the ReactCSSTransitionGroup is not rendered when item.length === 0.
2. Wrap table body into ReactCSSTransitionGroup
Codepen: https://codepen.io/skyshell/pen/RpbKVb
Here, the entire renderTBodyContent method is wrapped into ReactCSSTransitionGroup within the render method:
<ReactCSSTransitionGroup
component="tbody"
transitionName="example"
transitionEnter={false}
transitionLeave={true}>
{this.renderTBodyContent()}
</ReactCSSTransitionGroup>
And the RenderTBody method looks like:
renderTBodyContent: function() {
var items = this.state.items;
if (items.length === 0) {
return (
<tr><td colSpan="2">TABLE EMPTY</td></tr>
);
}
const rows = this.state.items.map(function(name) {
return (
<tr key={name}>
<td>{name[0]}</td>
<td>{name[1]}</td>
</tr>
);
});
return rows;}
The issue is that the empty state gets animated too.
Any suggestions on how to obtain the desired behaviour?
Thanks!
Thank you realseanp for your pointers. Using the low level API and TweenMax instead of CSS transitions, I came up with the following solution. First, introduce a Row component:
var Row = React.createClass({
componentWillLeave: function(callback) {
var el = React.findDOMNode(this);
TweenMax.fromTo(el, 1, {opacity: 1}, {opacity: 0, onComplete: callback})
},
componentDidLeave: function() {
this.props.checkTableContent();
},
render: function() {
const name = this.props.name;
return (
<tr>
<td>{name[0]}</td>
<td>{name[1]}</td>
</tr>
);
}
});
Then populate the table based on an isEmpty flag:
var Table = React.createClass({
getInitialState: function() {
return {
items: [],
isEmpty: true
};
},
addRow: function() {
var items = this.state.items;
var firstName = firstNames[Math.floor(Math.random() * firstNames.length)];
var lastName = lastNames[Math.floor(Math.random() * lastNames.length)];
items.push([firstName, lastName]);
this.setState({items: items, isEmpty: false});
},
removeLastRow: function() {
var items = this.state.items;
if (items.length != 0) {
items.splice(-1, 1);
this.setState({items: items});
}
},
checkTableContent: function() {
if (this.state.items.length > 0) {
this.setState({isEmpty: false});
}
else {
this.setState({isEmpty: true});
this.forceUpdate();
}
},
renderTBodyContent: function() {
if (this.state.isEmpty) {
return (
<tr><td colSpan="2">TABLE EMPTY</td></tr>
);
}
var that = this;
const rows = this.state.items.map(function(name) {
return <Row
key={name}
name={name}
checkTableContent={that.checkTableContent} />;
});
return rows;
},
render: function() {
return (
<div>
<button onClick={this.addRow}>Add row</button>
<button onClick={this.removeLastRow}>Remove row</button>
<table>
<thead>
<tr>
<th>First name</th>
<th>Last name</th>
</tr>
</thead>
<ReactTransitionGroup
component="tbody"
transitionName="example"
transitionEnter={false}
transitionLeave={true}>
{this.renderTBodyContent()}
</ReactTransitionGroup>
</table>
</div>
);
}
});
Codepen: https://codepen.io/skyshell/pen/yMYMmv

Generate Table dynamically display error Uncaught Error: Invariant Violation

I'm creating a table that uses data from a json, the json "policies" change when I click on different links in the page, the thing is that when I click and the state change, I have to generate the table again with the new json, but I get an
Uncaught Error: Invariant Violation: processUpdates(): Unable to find child 1 of element. This probably means the DOM was unexpectedly mutated (e.g., by the browser), usually due to forgetting a <tbody> when using tables, nesting tags like <form>, <p>, or <a>, or using non-SVG elements in an <svg> parent. Try inspecting the child nodes of the element with React ID .0.1.0.2.3.1.1.2.0.1.
The first time the page loads the table is correctly generated.
module.exports = React.createClass({
onPoliciesChange: function (policiesStore) {
this.setState(policiesStore);
},
getInitialState: function () {
return {
policies: []
};
},
componentDidMount: function () {
this.unsubscribeAlertsStore = AlertsStore.listen(this.onPoliciesChange);
},
componentWillUnmount: function () {
this.unsubscribeAlertsStore();
},
cols: [
{ key: 'name', label: 'Name'},
{ key: 'severity', label: 'Severity' },
{ key: 'width', label: 'Width' },
{ key: 'pulsate', label: 'Pulsate' }
],
generateHeaders: function () {
var cols = this.cols; // [{key, label}]
// generate our header (th) cell components
return cols.map(function (colData) {
return <th key={colData.key}> {colData.label} </th>;
});
},
generateRows: function () {
var slf = this;
var cols = this.cols, // [{key, label}]
data = this.data;
//per each item
return this.state.policies.map(function (item) {
// handle the column data within each row
var cells = cols.map(function (colData) {
return <td> {item[colData.key]} </td>;
});
return <tr key={item.id}> {cells} </tr>;
});
},
render: function () {
var headerComponents = this.generateHeaders(),
rowComponents = this.generateRows();
return (
<table className="table table-condensed table-striped">
<thead> {headerComponents} </thead>
<tbody> {rowComponents} </tbody>
</table>
);
}
});
I just move the from the render to the function that creates the rows:
generateRows: function () {
var severity = this.renderSeverity();
var slf = this;
var cols = this.cols, // [{key, label}]
data = this.data;
//per each item
return this.state.policies.map(function (item) {
// handle the column data within each row
var cells = cols.map(function (colData) {
if (colData.key === 'width') {
//return <td> <input type="checkbox" name="vehicle" value="Bike" onChange={slf.onChange.bind(null, id) }/></td>;
return <td> <input type="checkbox" onChange={slf.onChangeWidth.bind(null, item.id) }/></td>;
} else if (colData.key === 'pulsate') {
return <td> <input type="checkbox" onChange={slf.onChangePulsate.bind(null, item.id) }/></td>;
} if (colData.key === 'policyCheck') {
return <td> <input type="checkbox" onChange={slf.onChangePolicy.bind(null, item.id) }/></td>;
} else if (colData.key === 'severity') {
// colData.key might be "firstName"
return <td>{item[colData.key]} {slf.renderSeverity(item.severity) }</td>;
} else {
// colData.key might be "firstName"
return <td> {item[colData.key]} </td>;
}
});
return <tbody><tr key={item.id}> {cells} </tr></tbody>;
});
}

Resources