Invariant Violation when trying to render array of table row elements - javascript

I am using React and I'm trying to render a table that will be dynamic in the number of rows depending on the result size.
However I am getting the following error:
Unhandled Rejection Invariant Violation: Objects are not valid as
React child (found: objects with keys {name}). If you meant to render
a collection of children, use an array instead or wrap the object
using createFragment(object).
I have the following code and loop in the render function :
let result = this.props.result.exactMatches;
var rows = [];
for(var i = 0; i< result.length; i++) {
rows.push(
<tr>
<td width="50%"> result[i].name </td>
<td width="50%"> result[i].position </td>
</tr>
);
}
Then in the return jsx element I have the following div element:
<div>
<table style={{width:'100%'}}>
<tbody>
<tr>
<th width="50%"> Name </th>
<th width="50%"> Position </th>
</tr>
{rows}
</tbody>
</table>
</div>
Thanks in advance!

The error log is pretty self explanatory but lets go through it carefully.
Objects are not valid as React child
That pretty much means that React does not know to render an array of React components in the form you are providing it.
If you meant to render a collection of children, use an array instead or wrap the object using createFragment(object)
I would suggest going through createFragment documentation. The issue described is similar yours.
The React rendering pipeline uses a couple of heuristics to improve performance. One of them requires the developer to distinguish adjacent elements of the same type (e.g. <tr />) using a unique key property in each one of them.
Onto the solution itself, I would suggest embracing a more functional programming approach. map() is a method used on arrays. Think of it as turn this array of cats into an array of dogs but let me show you how.
In your case, you want to turn an array of matches called exactMatches into rows of a table, and thats what we are going to do.
<div>
<table style={{width:'100%'}}>
<tbody>
<tr>
<th width="50%"> Name </th>
<th width="50%"> Position </th>
</tr>
this.props.result.exactMatches.map((match, index) => (
<tr key={`row-${match.name}-${index}`}>
<td width="50%">match.name</td>
<td width="50%">match.position</td>
</tr>
))
</tbody>
</table>
</div>

This is the correct way to do it.
let result = this.props.result.exactMatches;
var rows = [];
for(var i = 0; i< result.length; i++) {
rows.push(
{name: result[i].name , position:result[i].position}
);
}
and render it like this.
<div>
<table style={{width:'100%'}}>
<tbody>
<tr>
<th width="50%"> Name </th>
<th width="50%"> Position </th>
</tr>
{rows.map((rowData, index) =>{
return <tr key=`results-row-${rowData.name}-${index}`>
<td width="50%"> {rowData.name} </td>
<td width="50%"> {rowData.position} </td>
</tr>
})}
</tbody>
</table>
</div>

This is a correct way:
<div>
<table style={{width:'100%'}}>
<tbody>
<tr>
<th width="50%"> Name </th>
<th width="50%"> Position </th>
</tr>
{this.props.exactMatches.map((item, i) => <tr key={i}>
<td width="50%"> {item.name }</td>
<td width="50%"> {item.position} </td>
</tr>)}
</tbody>
</table>
</div>
Pay attention for keys, if you have something unique value in each entity you can use it.

Related

Javascript - How to replace multiple HTML nodes

I'm building an e-mail constructor and when the user saves the template I just send the HTML to the server. But I need to remove the drap & drop element to send it to the server.
I'm not very good with DOM manipulation so I don't know where to start from.
This is my HTML:
<table>
<tbody>
<tr>
<th>
<div class="components-drop-area">
<p>aa</p>
<p>bb</p>
</div>
</th>
</tr>
<tr>
<th>
<div class="components-drop-area">
<p>cc</p>
<p>dd</p>
</div>
</th>
</tr>
</tbody>
</table>
I need to remove all the .components-drop-area divs. Something like that:
<table>
<tbody>
<tr>
<th>
<p>aa</p>
<p>bb</p>
</th>
</tr>
<tr>
<th>
<p>cc</p>
<p>dd</p>
</th>
</tr>
</tbody>
</table>
I stopped my code here:
var table = document.querySelector('table').cloneNode(true)
let dropAreas = table.querySelectorAll('.components-drop-area')
console.log(table, dropAreas)
How can I loop and remove desired elements while retaining their content?
One way would simply be to replace the parentNode's innerHTMLs with the .components-drop-area innerHTMLs:
let dropAreas = document.querySelectorAll('.components-drop-area');
for (let i = 0; i < dropAreas.length; i++) {
dropAreas[i].parentNode.innerHTML = dropAreas[i].innerHTML;
}
// The <div> contents have now been extracted, and the <div> elements removed
console.log(document.querySelector('table').innerHTML);
<table>
<tbody>
<tr>
<th>
<div class="components-drop-area">
<p>aa</p>
<p>bb</p>
</div>
</th>
</tr>
<tr>
<th>
<div class="components-drop-area">
<p>cc</p>
<p>dd</p>
</div>
</th>
</tr>
</tbody>
</table>
Here's an adaption of James's vanilla JS implementation that should work
for (const node of document.querySelectorAll("table .components-drop-area")) {
const parent = node.parentNode;
while (node.children.length>0) {
let child = node.children[0];
node.removeChild(child);
parent.insertBefore(child, node);
}
parent.removeChild(node);
}
Looping over the elements is tricky since we're modifying the collection during the iteration
for (const node of document.querySelectorAll("table .components-drop-area")) {
const parent = node.parentNode;
while (node.children.length>0) {
let child = node.children[0];
node.removeChild(child);
parent.insertBefore(child, node);
}
parent.removeChild(node);
}
// The <div> contents have now been extracted, and the <div> elements removed
console.log(document.querySelector('table').innerHTML);
<table>
<tbody>
<tr>
<th>
<div class="components-drop-area">
<p>aa</p>
<p>bb</p>
</div>
</th>
</tr>
<tr>
<th>
<div class="components-drop-area">
<p>cc</p>
<p>dd</p>
</div>
</th>
</tr>
</tbody>
</table>
If you want to use vanilla DOM operations, you would have to take each child from selected div elements and insert them into that node's parent element using the selected div itself as a reference before removing it. Every DOM node has a reference to its parent and its children, so you can do everything relative to each selected node like so:
for (const node of document.querySelectorAll("table .components-drop-area")) {
const parent = node.parentNode;
const children = Array.from(node.children);
for (const child of children) {
node.removeChild(child);
parent.insertBefore(child, node);
}
parent.removeChild(node);
}
An elegant solution using jQuery would be:
$('.components-drop-area').contents().unwrap();
console.log(document.querySelector('table').innerHTML);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<table>
<tbody>
<tr>
<th>
<div class="components-drop-area">
<p>aa</p>
<p>bb</p>
</div>
</th>
</tr>
<tr>
<th>
<div class="components-drop-area">
<p>cc</p>
<p>dd</p>
</div>
</th>
</tr>
</tbody>
</table>
Notes:
contents()
unwrap()

Trying to get data of the row clicked of the table react js

I am new to react,and making app where I want to get data of the row being clicked on a table,I don't whether this approach is good or not need suggestions
So far i have used onClick listener but when I click on a table , it gives me [object,object] in console.log(contract),I have also tried to to use loop to view data in it but it gives me [object,object],here's my below code:
<table id="mytable" cellSpacing="0" width="100%">
<thead>
<tr>
<th>CarName</th>
<th>DriverName</th>
<th>Date</th>
<th>Pickup</th>
</tr>
</thead>
<tbody>
{
this.state.contracts.map((contract,index)=>{
return(
<tr key={index} data-item={contract} onClick={this.contractdetails}>
<td>{contract.car} </td>
<td>{contract.driver}</td>
<td>{contract.date}</td>
<td>{contract.pickup}</td>
</tr>
)})
}
</tbody>
</table>
onClickfunction
contractdetails=(e)=>{
const contract=e.currentTarget.getAttribute('data-item');
console.log(contract)
};
Use JSON.stringify while setting the data-item
<tr key={index} data-item={JSON.stringify(contract)} onClick={this.contractdetails}>
And use JSON.parse() while accessing it.
contractdetails=(e)=>{
const contract= JSON.parse(e.currentTarget.getAttribute('data-item'));
console.log(contract)
};
A better way is to just set index as data-index and you can access it from the state
this.state.contracts.map((contract,index)=>{
return(
<tr key={index} data-index={index} onClick={this.contractdetails}>
...
...
}
You can access it like below
contractdetails=(e)=>{
let index = +e.currentTarget.getAttribute('data-index')
console.log(this.state.contracts[index]);
};

Create unique variable name inside *ngFor

I am trying to make a table that, when you click a button, it needs to display the row directly beneath it.
I have had a look at this post, but I could not find an answer from it.
When I have it like below, it works, but the problem is, it displays all other hidden rows since they all share the same collapse variable.
This is the working example, but not 100% correct:
<table>
<thead>
<th>Path out of this queue</th>
<th *ngFor="let role of roles">{{role.RoleName}}</th>>
</thead>
<tbody>
<ng-container *ngFor="let queue of workQueues; let i = index">
<tr>
<td><button (click)="collapse=!collapse">{{queue.WorkQueueName}}</button></td>
<td *ngFor="let role of roles">
<input type="checkbox" />
</td>
</tr>
<tr *ngIf="collapse">
Yay...
</tr>
</ng-container>
</tbody>
I thought I would be able to make the collapse variable unique, by appending the i, which is the index, to it, but then I get the following error:
Parser Error: Got interpolation ({{}}) where expression was expected
Here is my attempt:
<table>
<thead>
<th>Path out of this queue</th>
<th *ngFor="let role of roles">{{role.RoleName}}</th>>
</thead>
<tbody>
<ng-container *ngFor="let queue of workQueues; let i = index">
<tr>
<td><button (click)="{{collapse+i}}={{!collapse+i}}">{{queue.WorkQueueName}}</button></td>
<td *ngFor="let role of roles">
<input type="checkbox" />
</td>
</tr>
<tr *ngIf="{{collapse+i}}">
Yay...
</tr>
</ng-container>
</tbody>
Specifically, in my (click) event, how can I make a unique variable that could be used?
(click)="{{collapse+i}}={{!collapse+i}}"
should be
(click)="this[collapse+i] = !this[collapse+i]"
This allows you to use an indexer to obtain the field on the component. If it actually works depends on how you have collapse fields defined on your component.
Personally I would prefer extending the type contained in the workQueues array with an additional field.
(click)="queue.collapsed = !queue.collapsed"
...
<tr *ngIf="queue.collapsed">
An other alternative is to define a new field in the *ngFor.
<ng-container *ngFor="let queue of workQueues; let i = index; let isCollapsed = true">
<tr>
<td><button (click)="isCollapsed = !isCollapsed">{{queue.WorkQueueName}}</button></td>
<td *ngFor="let role of roles">
<input type="checkbox" />
</td>
</tr>
<tr *ngIf="!isCollapsed">
Yay...
</tr>
</ng-container>
stackblitz

Get the row index of bootstrap table in click without using jquery

I have a bootstrap table in my react project. I want to get the index of row which I click on. I want to do something like this onclick = {this.handleClick} and in handleClick function I want to get the index of row. Is it possible to do it. Most of the solutions available shows everything using jquery and I don't want to use Jquery. I want to do it using just javascript only. This is my table
<Table className='flags-table' responsive hover>
<thead>
<tr>
<th> </th>
<th> Time In </th>
<th> Time Out </th>
<th> Type </th>
<th> Category </th>
</tr>
</thead>
<tbody>
{
FLAGS_LIST.map((x,i)=> (
<tr key={i}>
<td> <div className='red-box'></div> </td>
<td> {x.time_in} </td>
<td> {x.time_out} </td>
<td> {x.type} </td>
<td> {x.category} </td>
</tr>
))
}
</tbody>
</Table>
You can use code like this:
onclick = {this.handleClick.bind(this, i)};
and handleClick should declare like this:
var handleClick = function(i) {
console.log("key of row", i)
...
};

Repeating a set of <td>s in the same table row using AngularJS

I want to create a table that, in its header, contains the names of each player for two teams in each . The scope has a variable teams which is a list of Team objects, each one having list of Player objects as an attribute.
This is turning out to be way harder than I expected.
<table>
<thead>
<tr>
<div ng-repeat="team in teams">
<th ng-repeat="player in team.players">{[ player.name ]}</th>
<th>Partial Score</th>
<th>Total Score</th>
</div>
</tr>
</thead>
</table>
is the easiest thing I have in mind - but this doesn't work. can't be placed inside a according to W3C (from what I understand), so the browser just takes the div and places it outside the table.
I've tried directives as well - but these don't help. This is an example of my directive file.
<th ng-repeat="player in team.players">{[ player.name ]}</th>
<th>Partial Score</th>
<th>Total Score</th>
For example, if I have a <player-initials> directive with replace: 'false', the browser expels the non-<th> element out of the table. Setting replace: 'true', fixes that, but then I get an error because there's more than one root element in the directive. I can't wrap the elements in the directive with anything because that would result in invalid HTML (as above).
I've tried all combinations of element and attribute directives and replace, nothing gives me what I want. Any help?
Created a fiddle here.
Hope this is what you are looking for, if not let me know the format that you are looking for. Your HTML should look something like below.
<table ng-repeat="team in teams">
<tr>
<td colspan="3">
{{team.name}}
</td>
</tr>
<tr>
<td>Player Name</td>
<td>Partial Score</td>
<td>Total Score</td>
</tr>
<tr ng-repeat="player in team.players">
<td>{{player.name}}</td>
<td>{{player.PScore}}</td>
<td>{{player.TScore}}</td>
</tr>
</table>
You should add a method to your scope that do the logic for you.
Something like this should work:
$scope.getAllPlayers = function() {
var players = [];
this.teams.forEach(function(team){
players = players.concat(team.players);
});
return players;
}
Then in your dom:
<table>
<thead>
<tr>
<th ng-repeat="player in getAllPlayers()">{{ player.name }}</th>
<th>Partial Score</th>
<th>Total Score</th>
</tr>
</thead>
</table>
I'm not using Angular so I may be wrong, but I'm pretty sure this is the way to go. This kind of logic have to be in your controller, as simple is a nested loop.
EDIT: Referring to the comments, you could make a function that return your header cells the way you want them
$scope.getHeaderCells = function() {
var cells = [];
this.teams.forEach(function(team){
cells = cells.concat(team.players.map(function(player){
return { team: team, value: player.name };
})).concat([
{team: team, value: 'Partial Score'},
{team: team, value: 'Total Score'}
]);
});
console.log(cells);
return cells;
}
with
<th ng-repeat="cell in getHeaderCells()">{{ cell.value }}</th>
here is a fiddle
I figured it out using the ng-repeat-start and ng-repeat-end directives. I really didn't want to write functions to generate HTML for me, because that's why I switched to Angular in the first place.
Here's a screenshot: http://i.imgur.com/e5LBvQB.png.
This is the (stripped-down to relevant part) code just for the header, everything else is similar. The only 'hacky' part I don't like is the ng-show attributes for B and T to reverse it properly, but it works for me right now. I think I can get clean it up with directives.
<table class="table table-condensed table-striped table-hover table-responsive">
<thead>
<tr>
<th ng-repeat-start="team in game.getTeams()" ng-hide="true"></th>
<th ng-repeat="player in team.getPlayers()" ng-class="$parent.$first && 'text-info' || 'text-danger'">
<u>
{[ player.name | truncate ]}
</u>
</th>
<th ng-repeat-end ng-show="$first">
#
</th>
</tr>
</thead>
</table>

Categories