iterate over zip iterable to produce multiple rows in table - Reactjs - javascript

I have a database of arrays that I want to unpack and insert into a table.
For example I'll have an array called a=[1,2,3,4] and b=['a','b','c','d'] each with and equal length.
And I will have a table with just the headers
a
b
My generated array would be
[[1,'a'],[2,'b'],[3,'c'],[4,'d']] created with the zip function from the underscore package.
My goal is to iterate over this array and generate the following
a
b
1
'a'
2
'b'
3
'c'
4
'd'
At the moment, I have a
function returnIt(){
let _ = require('underscore')
//returns [[1,'a'],[2,'b'],[3,'c'],[4,'d']] so x[0] = 1 and x[1] = 'a'
for (var x of _.zip([1,2,3,4],['a','b','c','d'])){
return (
<>
<td>
{x[0]}
</td>
<td>
{x[1]}
</td>
</>
)
}
return(
<table>
<thead>
<tr>
<th>a</th>
<th>b</th>
</tr>
</thead>
<tbody>
<tr>
{returnIt()}
</tr>
</tbody>
</table>
)
But this doesn't work. I get
As you can see I only get one row, the code does not produce more than one row! Sorry about the headers, I tried changing my program as much as I could to suit your eyes.
Anyways, how come this is my result and what can I change?

In returnIt, your for loop returns in the code block so it will only run once and return the first pair of array elements you've transformed to html elements. Try returning a mapping of the zipped elements to markup fragments. Then you’ll see them all.
Here's a complete example:
import _ from "underscore";
export default function App() {
return(
<table>
<thead>
<tr>
<th>a</th>
<th>b</th>
</tr>
</thead>
<tbody>
{returnIt()}
</tbody>
</table>)
}
function returnIt(){
//returns [[1,'a'],[2,'b'],[3,'c'],[4,'d']] so x[0] = 1 and x[1] = 'a'
let zipped = _.zip([1,2,3,4],['a','b','c','d'])
return zipped.map(pair => {
return (
<tr>
<td>
{pair[0]}
</td>
<td>
{pair[1]}
</td>
</tr>
)
})
}

Assuming this is using React
return (
<>
<td>
{x[0]}
</td>
<td>
{x[1]}
</td>
</>
)
<td> should be direct descendant of <tr>

The problem is you're returning from the loop body. So it'll only ever get to the first element.
for (anything) {
return ...
}
will always return immediately without continuing the loop, no matter what is between the parentheses.
You need to have the loop build up the HTML and return all the rows at once, or else maybe use something like a generator function and add a loop to the place that calls the function.
Assuming you're using some framework that lets you just return unquoted HTML like that, the problem with the table part of your return value is probably that you can't have a <div> between a <tr> and one of its enclosed <td>s.

Related

How to list individual key values in an object in React

I'm currently creating a table through React where the labels are certain object keys and the table entries are the object values. The set of data I'm using is an array of objects where I use map to get each individual object. Here's how I'm doing it.
{
data.map(graph =>
<div key={graph.ID} className="tables">
<table>
<tr>
<th>{Object.keys(graph)[0]}</th>
<th>{Object.keys(graph)[1]}</th>
<th>{Object.keys(graph)[2]}</th>
<th>{Object.keys(graph)[3]}</th>
<th>{Object.keys(graph)[4]}</th>
<th>{Object.keys(graph)[5]}</th>
<th>{Object.keys(graph)[6]}</th>
<th>{Object.keys(graph)[7]}</th>
<th>{Object.keys(graph)[8]}</th>
<th>{Object.keys(graph)[9]}</th>
<th>{Object.keys(graph)[10]}</th>
<th>{Object.keys(graph)[11]}</th>
<th>{Object.keys(graph)[12]}</th>
<th>{Object.keys(graph)[13]}</th>
</tr>
<tr>
<td>{Object.values(graph)[0]}</td>
<td>{Object.values(graph)[1]}</td>
<td>{Object.values(graph)[2]}</td>
<td>{Object.values(graph)[3]}</td>
<td>{Object.values(graph)[4]}</td>
<td>{Object.values(graph)[5]}</td>
<td>{Object.values(graph)[6]}</td>
<td>{Object.values(graph)[7]}</td>
<td>{Object.values(graph)[8]}</td>
<td>{Object.values(graph)[9]}</td>
<td>{Object.values(graph)[10]}</td>
<td>{Object.values(graph)[11]}</td>
<td>{Object.values(graph)[12]}</td>
<td>{Object.values(graph)[13]}</td>
</tr>
</table>
</div>
)
}
As you can see, each entry on the table is a different key/value, and graph represents each individual object in the array. The problem is that some objects have more than 14 keys so I want to list out every key and value in each object within JSX regardless of the size. Does anyone know how to do that? I've tried using for loops (which is usually how I list out individual keys) but I can't seem to do that within JSX.
Use Array.map() for each row. For the th iterate the keys, and for the td use Object.entries() to get both the keys (for the item's keys) and values:
{
data.map(graph => (
<div key={graph.ID} className="tables">
<table>
<tr>
{
Object.keys(graph)
.map(key => <th key={key}>{key}</th>)
}
</tr>
<tr>
{
Object.entries(graph)
.map(([key, val]) => <th key={key}>{val}</th>)
}
</tr>
</table>
</div>
))
}
I think you could do something like this:
{
data.map(graph => {
return (
<div key={graph.ID} className="tables">
<table>
<tr>
{Object.keys(graph).map(key => {
return <th>{key}</th>
})}
</tr>
<tr>
{Object.keys(graph).map(key => {
return <th>{graph[key]}</th>
})}
</tr>
</table>
</div>
)
})
}
You can just use map() method to do this:
<tr>
Object.keys(graph).map((key) => (<th>key</th>))
</tr>
<tr>
Object.values(graph).map((value) => (<td>value</td>))
</tr>
Note: this will most likely complain about absent key props, so you will have to add a key value to each element. (using the key value of graph object is one option)

Make the data selection from object to be dry

Quick question more on how should I approach this below to be dry. I have data which comes from the backend and on front i use react I have a component which is basically a table. Two api calls witch return different objects. I want to reuse one component rather than creating two separate tables as below. I pass an object of data to a table component, just need to know according to the object which keys to select.
<table>
<tbody>
<tr>
<td>{name}</td>
<td>{first_test.week_day}</td>
<td>{first.four.three}</td>
</tr>
</tbody>
</table>
<table>
<tbody>
<tr>
<td>{name}</td>
<td>{test.time}</td>
<td>{another.one.two}</td>
</tr>
</tbody>
</table>
two separate api requests example:
{
first: {four: {three: "different"}},
first_test: {week_day: 'Saturday'},
name: "first test"
}
{
another: {one: {two: "nice"}},
test: {time: 10:00},
name: "test"
}
so what would be a best way to approach this being dry without creating multiple components ? maybe some json schema?
It might be duplicate if someone drops the another related question would appreciate.
You conform whatever input to match your generic table component like so
function GenericTable({first, second, third}) {
return (
<table>
<tbody>
<tr>
<td>{first}</td>
<td>{second}</td>
<td>{third}</td>
</tr>
</tbody>
</table>
)
}
and call it like
<GenericTable first={name} second={first_test.week_day} third={first.four.three} />
or
<GenericTable first={name} second={test.time} third={another.one.two} />
update 1 based on comment
function GenericTable({ columns }) {
columnstb = columns.map((column, index) => (<td key={index}>{column}</td>);
return (
<table>
<tbody>
<tr>
{columnstb}
</tr>
</tbody>
</table>
)
}
and call it like
<GenericTable columns={[name, first_test.week_day, first.four.three]} />

How to iterate through table cells and rows in React when I must iterate through multiple data arrays?

I am trying to implement a table but since I have to iterate through multiple arrays for each row, my items wont get rendered in the correct row or are but the height gets messed up since I don't think I implement the table properly. Could someone please help?
<table id="customers">
<thead>
<tr>
<th>Country</th>
<th>Cities</th>
<th>Shops</th>
<th>Map</th>
</tr>
</thead>
<tbody>
<tr>
<td> {this.renderCountries()}</td>
<td> {this.state.renderCities ? this.renderCities() :
<p>{this.state.defaultCity}</p>} </td>
<td> {this.state.renderShops ? this.renderShops() :
<p>{this.state.defaultShopName}</p>} </td>
<td> {this.state.renderMap ? this.renderMap() : null} </td>
</tr>
</tbody>
</table>
in my methods (renderCountries etc), I iterate through the particular array and return another component
renderCountries = () => {
return this.state.countries.map((country, index) => {
return (
<Column
key={index}
data={country}
/>
)
})
};
const Column = ({data}) => (<p> {data}</p>);
The way I have it so far is the only one that at least renders all items in the correct cell, however, it messes up the CSS (the second row, all items get moved about 200px down), I think its because I m not implementing the table properly, but when I change that to anything else, items wont get rendered correctly. If more info is needed please let me know.

NodeJS: How can I scrape two different tables, that are visually part of the same table, into one JSON Object?

Here's an example of the table of data I'm scraping:
The elements in red are in the <th> tags while the elements in green are in a <td> tag, the <tr> tag can be displayed according to how they're grouped (i.e. '1' is in it's own <tr>; HTML snippet:
EDIT: I forgot to add the surrounding div
<div class="table-cont">
<table class="tg-1">
<thead>
<tr>
<th class="tg-phtq">ID</td>
</tr>
</thead>
<tbody>
<tr>
<td class="tg-0pky">1</td>
<td class="tg-0pky">2</td>
<td class="tg-0pky">3</td>
</tr>
</tbody>
</table>
<table class="tg-2">
<thead>
<tr>
<th class="tg-phtq">Sample1</td>
<th class="tg-phtq">Sample2</td>
<...the rest of the table code matches the pattern...>
</tr>
</thead>
<tbody>
<tr>
<td class="tg-0pky">Swimm</td>
<td class="tg-dvpl">1:30</td>
<...>
</tr>
</tbody>
<...the rest of the table code...>
</table>
</div>
As you can see, in the HTML they're actually two different tables while they're displayed in the above example as only one. I want to generate a JSON object where the keys and values include the data from the two tables as if they were one, and output a single JSON Object.
How I'm scraping it right now is a bit of modified javascript code I found on a tutorial:
EDIT: In the below, I've been trying to find a way to select all relevant <th> tags from both tables and insert them into the same array as the rest of the <th> tag array and do the same for <tr> in the table body; I'm fairly sure for the th I can just insert the element separately before the rest but only because there's a single one - I've been having problems figuring out how to do that for both arrays and make sure all the items in the two arrays map correctly to each other
EDIT 2: Possible solution? I tried using XPath Selectors and I can use them in devTools to select everything I want, but page.evaluate doesn't accept them and page.$x('XPath') returns JSHandle#node since I'm trying to make an array, but I don't know where to go from there
let scrapeMemberTable = async (page) => {
await page.evaluate(() => {
let ths = Array.from(document.querySelectorAll('div.table-cont > table.tg-2 > thead > tr > th'));
let trs = Array.from(document.querySelectorAll('div.table-cont > table.tg-2 > tbody > tr'));
// the above two lines of code are the main problem area- I haven't been
//able to select all the head/body elements I want in just those two lines of code
// just removig the table id "tg-2" seems to deselect the whole thing
const headers = ths.map(th => th.textContent);
let results = [];
trs.forEach(tr => {
let r = {};
let tds = Array.from(tr.querySelectorAll('td')).map(td => td.textContent);
headers.forEach((k,i) => r[k] = tds[i]);
results.push(r);
});
return results; //results is OBJ in JSON format
}
}
...
results = results.concat( //merge into one array OBJ
await scrapeMemberTable(page)
);
...
Intended Result:
[
{
"ID": "1", <-- this is the goal
"Sample1": "Swimm",
"Sample2": "1:30",
"Sample3": "2:05",
"Sample4": "1:15",
"Sample5": "1:41"
}
]
Actual Result:
[
{
"Sample1": "Swimm",
"Sample2": "1:30",
"Sample3": "2:05",
"Sample4": "1:15",
"Sample5": "1:41"
}
]

How to correctly setup a conditional expression (if) in a React render()

I want to be able to rendering a certain section of HTML if the condition is true. I am curious about the correct way to setup a conditional if expression within a react render().
I looked it up online and found one way to do this with a inline expression to check if the value is true, if so then it will render the remaining element.
I also setup another way to create variables for the html to be rendered.
Question:
I was unable to wrap both td tags as one for the condition. It looks like this needs to be done per td tag.
Is there a way to do this around both tags or does it require setting up another element around them?
I thought this could also be setup using a => function possibly.
Code for inline render() expression:
render() {
// get the data from the JSON entity for each attribute
var tdsForObject = this.props.jsonAttributes.map(jsonAttribute =>
<td>{this.props.object.entity[jsonAttribute]}</td>
);
return (
<tbody>
<tr>
{tdsForObject}
{this.props.objectTypeEditable &&
<td>
<UpdateDialog object={this.props.object}
objectName={this.props.objectName}
attributes={this.props.attributes}
onUpdate={this.props.onUpdate}/>
</td>
}
{this.props.objectTypeEditable &&
<td>
<button onClick={this.handleDelete}>Delete</button>
</td>
}
</tr>
</tbody>
)
}
Code to create buttons outside of render()
render() {
// get the data from the JSON entity for each attribute
var tdsForObject = this.props.jsonAttributes.map(jsonAttribute =>
<td>{this.props.object.entity[jsonAttribute]}</td>
);
var updateButton;
var deleteButton;
// if the object can be edited create the update and delete buttons
if (this.props.objectTypeEditable) {
updateButton = (
<td>
<UpdateDialog object={this.props.object}
objectName={this.props.objectName}
attributes={this.props.attributes}
onUpdate={this.props.onUpdate}/>
</td>
);
deleteButton = (
<td>
<button onClick={this.handleDelete}>Delete</button>
</td>
);
}
return (
<tbody>
<tr>
{tdsForObject}
{updateButton}
{deleteButton}
</tr>
</tbody>
)
}
JSX doesn't allow you to return 2 side by side elements. It can only return 1 element. So yeah you can either wrap those 2 inside a single element and use the same verification as you do now.
{this.props.objectTypeEditable &&
<div class="wrapper">
<td>
[...]
</td>
<td>
[...]
</td>
</div>
}
You can also use inline self invoked function and return an array of JSX elements. (the render methods will automatically loop through them and render them). Here I use ES6 arrow function to bind directly the this reference but it is probably doable with a normal function and binding it by hand like this .bind(this)
{(() => {
let elements = [];
if(this.props.objectTypeEditable) {
// push td elements in array
}
return elements;
})()}
you need to use a ternary expression
condition ? expr1 : expr2
render() {
// get the data from the JSON entity for each attribute
var tdsForObject = this.props.jsonAttributes.map(jsonAttribute =>
<td>{this.props.object.entity[jsonAttribute]}</td>
);
return (
<tbody>
<tr>
{tdsForObject}
{ this.props.objectTypeEditable
? <td>
<UpdateDialog object={this.props.object}
objectName={this.props.objectName}
attributes={this.props.attributes}
onUpdate={this.props.onUpdate}/>
</td>
: null
}
{ this.props.objectTypeEditable
? <td>
<button onClick={this.handleDelete}>Delete</button>
</td>
: null
}
</tr>
</tbody>
)
}
It is not possible to use more than one inline. React's documentation and examples use ternary operations and recommends it as the default pattern. If you prefer one method over the other thats fine, they are both valid, just stick to one for consistency's sake :)

Categories