React.js- Dynamic table with rowspan - javascript

I am trying to create a dynamic table, using this example- Dynamic & Complex rowspan in HTML table
I am trying to replicate the similar thing in react.js
I have put a closing tr tag in conditional rendering if the index is 0.
However, it gives me ' Expected corresponding JSX closing tag for <>'
The state 'data' object which I got from back-end is something like -
{
"100": [
"ABC"
],
"101": [
"123",
"DEF",
"XYZ",
"456",
"657",
"1234",
"4564",
"vdfgx",
"vcegefgg",
"g544"
]
}
I have written following code in component to create dynamic table.
<table>
<thead>
<tr>
<th>Code</th>
<th>Reason</th>
</tr>
</thead>
<tbody>
{Object.keys(this.state.data).map(
(error_code, i) => {
return (
<React.Fragment>
<tr>
<td
rowSpan={
this.state.data[
error_code
].length
}
key={i}
>
{error_code}
</td>
{this.state.data[
error_code
].map((error_description, index) => (
<React.Fragment>
{index === 0 ? (
<React.Fragment>
<td
key={index}
rowSpan="1"
>
{error_description}
</td>
</tr> //<--- HERE
</React.Fragment>
) : (
<tr>
<td
key={index}
rowSpan="1"
>
{error_description}
</td>
</tr>
)}
</React.Fragment>
))}
</React.Fragment>
);
}
)}
</tbody>
</table>
I want the final output something like -
<tbody>
<tr>
<td rowspan="1">100</td>
<td rowspan="1">ABC</td>
</tr>
<tr>
<td rowspan="10">101</td>
<td rowspan="1">123</td>
</tr>
<tr>
<td rowspan="1">DEF</td>
</tr>
<tr>
<td rowspan="1">XYZ</td>
</tr>
<tr>
<td rowspan="1">456</td>
</tr>
.....
</tbody>
Could anyone please guide me the correct way to achieve the desired output ?

JSX, like XML, requires proper nesting <><tr>...</tr></> and does not support overlapping tags like <><tr></></tr>. You need to structure your code in a way to preserve proper nesting:
<tbody>
{Object.keys(this.state.data).map((error_code, i) => {
return this.state.data[error_code].map((error_description, index) =>
index === 0 ? (
<tr>
<td rowSpan={this.state.data[error_code].length} key={i}>
{error_code}
</td>
<td key={index} rowSpan="1">
{error_description}
</td>
</tr>
) : (
<tr>
<td key={index} rowSpan="1">
{error_description}
</td>
</tr>
)
)
})}
</tbody>

JSX does not generate a string. It generates a tree-like structure built out of function calls and Objects. Any <tag></tag> translates into a function call, that's why your code is invalid.
You need to rethink this component.
Split the data processing and decision making into a separate function that does only that and returns the final data to be rendered. Then loop/map through it in render and return the elements structure.

Related

Suggestion to refactor code into simple way -React

I have multiple with the same class name and method with different parameter
i want to refactor the below code to a simpler way any suggestion would be helpful.
<table class="greyGridTable">
<tbody>
<tr>
<td>AA</td>
<td className = 'table-container'>{formatDate(someMethod1(param1,a)}</td>
</tr>
<tr>
<td>BB</td>
<td className = 'table-container'>{formatDate(someMethod1(param1,b)}</td>
</tr>
<tr>
<td>CC</td>
<td className = 'table-container'>{formatDate(someMethod1(param1,c)}</td>
</tr>
</tbody>
</table>
I want to refactor the code with the same component.
I hope this would be helpful. thanks
export const TableItems = ({data}) => {
return (
<>
{data.map(item => (
<tr>
<td>{item.name}</td>
<td className='table-container'> {item?.symbol} {formatDate(someMethod1(param1,a)}</td>
</tr>
))}
</>
)
}
const data = [
{
name: AA,
},
{
name: BB,
},
{
name: CC,
symbol: '£'
}
]
<table class="greyGridTable">
<tbody>
<TableItems data={data} />
</tbody>
</table>
Try to use an array instead. Having reusable components in an array is easy to maintain, expand and read. Definitely a good practice.
let tableItems = [{name:"AA",date:new Date()},.....]
return (
<table class="greyGridTable">
<tbody>
{tableItems.map((item,index)=>{
return(
<tr>
<td>{item.date}</td>
<td className = 'table-container'>{item.date}</td>
</tr>
)
})}
</tbody>
</table>
)

Read more and read less for paragraph in react

I want to add read-more and read-less functions to a paragraph using class
I am rendering components like this
<table border="1">
<tbody>
{data
? Object.keys(data).map((item) => {
return (
<tr key={"tr" + item}>
{data[item]
? Object.keys(data[item]).map((key) => {
return (
<td
key={"td" + key}
className={key === "description" && "read-more"}
>
{data[item][key]}
</td>
);
})
: null}
</tr>
);
})
: null}
</tbody>
</table>
And the output is this
<table border="1">
<tbody>
<tr>
<td>New expense</td>
<td>default</td>
<td>10000</td>
<td class="read-more">
Long paragraph..
</td>
</tr>
<tr>
<td>New New exp</td>
<td>default</td>
<td>123</td>
<td class="read-more">
Long paragraph.. not displaying due to stack overflow too much code thing
</td>
</tr>
</tbody>
</table>
So I am making many paragraphs using objects and this runs in other components too
So all I want is a function in which it for each or use any loop to get elements class named read-more and add function to it in which it creates an anchor tag for read-more and read-less by only using a class to p tag
Please solve my problem. Thanks

how do I fix my code in order to display one table?

I am trying to render my dynamic JSON , but I am getting a table per result when it should be rendered in the same table.. what's happening this? my dummy array is a model from my backend api
const arr = [
{
"Demo": [
{
"_id": "T08210",
"name": "tilehaha",
"tags": [
"Demo"
],
"queries": [],
"urls": [],
"max_leght": [],
"count_items": []
},
],
}
];
export default function Demo() {
return (
<div>
{arr.map(obj =>
Object.entries(obj).map(([key, value]) => (
<table class="table">
<thead>
<tr key={key}>
<th scope="col">{key}</th>
</tr>
</thead>
<tbody>
<tr>
<td>{value.activityType}</td>
<td>{value.durationInSeconds}</td>
</tr>
</tbody>
</table>
)))}
</div>
);
}
I need to place in the blue section as heading 2671161009, and 2671161249 , and their child items as the following
Try this.
<table class="table">
<thead>
<tr>
{arr.map(obj => Object.entries(obj).map(([key, value]) => (
<th scope="col">{key}</th>
)))}
</tr>
</thead>
<tbody>
{arr.map(obj => Object.entries(obj).map(([key, value]) => (
<tr>
<td>{value.activityType}</td>
<td>{value.durationInSeconds}</td>
</tr>
)))}
</tbody>
</table>
Based on your comments, it seems like maybe this is what you want:
Note to future views -- now not applicable to the changed question:
const modArray = Object.keys(arr[0]).map(i => {
return {id: i, ...arr[0][i]}
});
const rowKeys = Object.keys(modArray[0]).filter(i => i !== "id")
export default function Demo() {
return (
<div>
<table class="table">
<thead>
<tr>
{modArray.map(i => <th key={i.id}>{i.id}</th>)}
</tr>
</thead>
<tbody>
<tr>
{modArray.map(item => <td key={item.id}>{item.activityType}</td>)}
</tr>
<tr>
{modArray.map(item => <td key={item.id}>{item.durationInSeconds}</td>)}
</tr>
{/* Or, do it dynamically */}
{rowKeys.map(rowKey => <tr key={rowKey}>
{modArray.map(item => <td key={item.id}>{item[rowKey]}</td>)}
</tr>)}
</tbody>
</table>
</div>
);
}
Note that at the top of that code sample, I transformed your input into a more usable format. Your beginning arr, for example, was an array of just 1 object, which had multiple keys, which really seemed to be the array you wanted to iterate over.
Hopefully this is at least close to what you're looking for.
Update 2, based on your changed question:
export default function Demo() {
return (
<div>
<table className="table">
<thead>
<tr>
{arr[0]["Demo"].map(i => <th key={i._id}>{i._id}</th>)}
</tr>
</thead>
<tbody>
<tr>
{arr[0]["Demo"].map(item => <td key={item._id}>{item.name}</td>)}
</tr>
</tbody>
</table>
</div>
);
}
The issue is here:
{arr.map(obj =>
Object.entries(obj).map(([key, value]) => (
<table class="table">
here you have put the entire table inside the loop instead of putting a single table row. So on every iteration it generates a new table. To resolve this issue, try this:
Try this:
<table class="table">
<thead>
<tr>
<th scope="col">Heading 1</th>
<th scope="col">Heading 2</th>
</tr>
</thead>
<tbody>
{
arr.map(obj =>
Object.entries(obj).map(([key, value]) => (
<tr>
<td>{value.activityType}</td>
<td>{value.durationInSeconds}</td>
</tr>
)))}
</tbody>
</table>

how to generate unique id for table cells using map

I want to set a unique id for each MenuItem, but I don't know how to do this with map() function nested in another one
<table className={classes.table}>
<thead>
<tr>
<td />
{sit.sit.map(sit => (
<td className={classes.sitCell} align="center" key={sit}>
{sit}
</td>
))}
</tr>
</thead>
<tbody>
{sit.row.map(row => (
<tr key={row}>
<td className={classes.rowCell} align="left">
{row}
</td>
{sit.sit.map(sit => (
<td className={classes.sit} key={(id = id + 1)}>
<MenuItem
id={sitId}
onClick={handleSitClick}
disabled={selected}
className={classes.sit}
/>
</td>
))}
</tr>
))}
</tbody>
</table>
Provided code looks fine for me. You can use uuid or any other similar package to generate keys.
Your question is not fully clear i think its may be help you .
<table className={classes.table }>
<thead>
<tr>
<td></td>
{
sit.sit.map((sit) => (<td className={classes.sitCell} align='center' key={sit}>{sit}</td>))
}
</tr>
</thead>
<tbody>
{
sit.row.map((row,index )=> (
<tr key={index}>
<td className={classes.rowCell} align='left'>{row}</td>
{
sit.sit.map((sit) => (<td className={classes.sit} key={id = id+1}><MenuItem id={sitId} onClick={handleSitClick} disabled={selected} className={classes.sit}></MenuItem></td>))
}
</tr>
))
}
</tbody>
</table>
you can just add (row,index) like this
Ideally you need to use a key which is some kind of unique id from your api response. Check your api response data structure, if it has an unique id, then use it. Else use the map array index.
Like this
<table className={classes.table}>
<thead>
<tr>
<td></td>
{
sit.sit.map((sit) => (<td className={classes.sitCell} align='center' key={sit}>{sit}</td>))
}
</tr>
</thead>
<tbody>
{
sit.row.map(row => (
<tr key={row}>
<td className={classes.rowCell} align='left'>{row}</td>
{
sit.sit.map((sit, index) => (<td className={classes.sit} key={index}><MenuItem id={index} onClick={handleSitClick} disabled={selected} className={classes.sit}></MenuItem></td>))
}
</tr>
))
}
</tbody>
</table>

React JS - How do I optimize render code?

I have this code in my render function, but I have 5 different versions with minor html changes. I made new tables with each of the 5. How would I optimize it so I do not have to repeat a lot of html/js code?
<table>
<thead>
<tr>
<th></th>
<th className='desc-col'>Description</th>
<th className='button-col'>Amount</th>
</tr>
</thead>
<tbody> { this.showData
this.state.data.map((exp) => {
if (exp.typeOfItem === "Asset" && exp.term == "Short-Term" ) {
return <tr>
<td className='counterCell'></td>
<td className='desc-col'>{exp.description}</td>
<td className='button-col'>${exp.amount}</td>
<td className='button-col'>
<Update expense={exp} />
</td>
<td className='button-col'>
<Delete expense={exp} />
</td>
</tr>
}
})
}
</tbody>
</table>
<table>
<thead>
<tr>
<th></th>
<th className='desc-col'>Description</th>
<th className='button-col'>Amount</th>
</tr>
</thead>
<tbody>
{
this.state.data.map((exp) => {
if (exp.typeOfItem === "Asset" && exp.term == "Long-Term" ) {
return <tr>
<td className='counterCell'></td>
<td className='desc-col'>{exp.description}</td>
<td className='button-col'>${exp.amount}</td>
<td className='button-col'>
<Update expense={exp} />
</td>
<td className='button-col'>
<Delete expense={exp} />
</td>
</tr>
}
})
}
</tbody>
</table>
You can pull out your Table in a custom component and pass down the data as props,
Your new component would be,
import React from 'react'
const TableComponent = (props) => (
<table>
<thead>
<tr>
<th></th><th className='desc-col'>Description</th>
<th className='button-col'>Amount</th>
</tr>
</thead>
<tbody>
{
props.data.map((exp) => {
if (exp.typeOfItem === props.typeOfItem && exp.term === props.term ) {
return <tr>
<td className='counterCell'></td>
<td className='desc-col'>{exp.description}</td>
<td className='button-col'>${exp.amount}</td>
<td className='button-col'> <Update expense={exp}/></td>
<td className='button-col'><Delete expense={exp} /></td>
</tr>
}
})
}
</tbody>
</table>
)
export default TableComponent
Now you can render this component by passing props,
<TableComponent data={this.state.data} typeOfItem="Asset" term="Short-Term"/>
<TableComponent data={this.state.data} typeOfItem="Asset" term="Long-Term"/>
Note: If you have any other variable's to be used in Table, do pass them as props and in your TableComponent use them appropriately.
You would be better off splitting the array before the render.
for instance:
const assets = this.state.data.filter(item => item.typeOfItem === "Asset");
const longTerm = [];
const shortTerm = [];
assets.forEach((asset) => {
asset.term = "long" ? longTerm.push(asset) : shortTerm.push(asset);
});
Next you can render it with a component you want
longTerm.map(asset => {
return <MyComponent amount={asset.amount} ... />
})
shortTerm.map(asset => {
return <MyComponent amount={asset.amount} ... />
})
And your component could be
function MyComponent(props) {
return <tr>
<td className='counterCell'></td>
<td className='desc-col'>{props.description}</td>
//rest
</tr>
}
additionally you could make a table component and pass it the collection which calls MyComponent
function TableComponent({collection}) {
return <table>
<thead>
<tr>
<th></th><th className='desc-col'>Description</th>
<th className='button-col'>Amount</th>
</tr>
</thead>
<tbody>
{
collection.map(asset => {
return <MyComponent ....
});
}
</tbody>
</table>
}
and then the initial render would just be
<>
<TableComponent collection={longterm} />
<TableComponent collection={shortterm} />
</>

Categories