I'm creating a table in React (I'm new to React), but the CategoryDataCan we live with those or should we use something else? It is not creating cells correctly, i.e. it's creating cells that are not aligned with the <th> that come from the parent and they do not have cell borders at all. It's also giving these warnings:
Warning: validateDOMNesting(...): <div> cannot appear as a child of <tbody>. See Param > tbody > Row > div.
Warning: validateDOMNesting(...): <tr> cannot appear as a child of <div>. See Row > div > tr.
Warning: validateDOMNesting(...): <tr> cannot appear as a child of <div>. See CategoryData > div > tr.
I'm not sure why these warnings are happening and why the table cells(from CategoryData) are not getting aligned and don't have cell borders. What's the correct way to do it?
Code
var Param = React.createClass({
getInitialState: function() {
return {
isLoading: true,
panelTitle: "",
data: [],
categories: []
}
},
updateState: function() {
var that = this;
var categories = new Set();
rest.getParameters('service').then(function (results) {
for (var i = 0; i < results.data.length; i++) {
categories.add(results.data[i].category);
}
that.setState({
data: results.data,
categories: Array.from(categories)
})
}).catch(function (err) {
console.error('rest.getParameters(): ', err);
that.setState({
isLoading: true,
data: [],
categories: []
})
});
},
componentDidMount: function() {
this.updateState();
},
render: function() {
return (
<Panel className="panel-info" header={<h4 className="panel-title">Service Config</h4>}>
<div>
<table className="table table-striped table-bordered table-hover table-condensed table-responsive">
<thead>
<tr>
<th className="col-lg-2 text-center">AMP Name</th>
<th className="col-lg-2 text-center">Athena Name</th>
<th className="col-lg-2 text-center">Description</th>
<th className="col-lg-1 text-center">Default</th>
<th className="col-lg-1 text-center">Min Value</th>
<th className="col-lg-1 text-center">Max Value</th>
<th className="col-lg-1 text-center">Action</th>
</tr>
</thead>
<tbody>
{this.state.categories.map((category, index) =>
<th colSpan="7" key={index} style={{'textAlign':'left', 'paddingLeft':'5px', 'backgroundColor': '#D3D0CF'}}>{this.state.category}</th>
this.state.data.map((row, i) =>
if (row.category === category) {
<tr key={i}>
<td className="col-lg-2 text-center">{row.name}</td>
<td className="col-lg-2 text-center">{row.alias}</td>
<td className="col-lg-2 text-center">{row.description}</td>
<td className="col-lg-1 text-center">{row.default_value}</td>
<td className="col-lg-1 text-center">{row.min_value}</td>
<td className="col-lg-1 text-center">{row.max_value}</td>
<td className="col-lg-1 text-center">Action</td>
</tr>
}
)
)}
</tbody>
</table>
</div>
</Panel>
);
}
});
I would change the 'th' to a 'tr' because I'm pretty sure react will give you a warning if you add 'th' inside 'tbody'
let finalList = []
this.state.categories.forEach( (cat, index) => {
finalList.push(<tr...>{this.state.category}</tr>)
this.state.data.forEach( (row, index) => {
if(row.category === cat){
finalList.push(
<tr key={i}>
<td className="col-lg-2 text-center">{row.name}</td>
<td className="col-lg-2 text-center">{row.alias}</td>
<td className="col-lg-2 text-center">{row.description}</td>
<td className="col-lg-1 text-center">{row.default_value}</td>
<td className="col-lg-1 text-center">{row.min_value}</td>
<td className="col-lg-1 text-center">{row.max_value}</td>
<td className="col-lg-1 text-center">Action</td>
</tr>
)
}
})
})
Word of warning I would avoid using tables checkout css grids their a lot more flexible and pretty well supported
EDIT: From version 16.0.0 onwards in react, you could make use of React.Fragment to return multiple elements from render
<tbody>
{
this.state.categories.map((category, index) => {
var innerData = this.state.data.map((row, i) => {
if (row.category === category) {
return (
<tr key={i}>
<td className="col-lg-2 text-center">{row.name}</td>
<td className="col-lg-2 text-center">{row.alias}</td>
<td className="col-lg-2 text-center">{row.description}</td>
<td className="col-lg-1 text-center">{row.default_value}</td>
<td className="col-lg-1 text-center">{row.min_value}</td>
<td className="col-lg-1 text-center">{row.max_value}</td>
<td className="col-lg-1 text-center">Action</td>
</tr>
)
}
return null
})
return (
<React.Fragment>
<th colSpan="7" key={index} style={{
'textAlign': 'left',
'paddingLeft': '5px',
'backgroundColor': '#D3D0CF'
}}>{this.state.category}</th>,
{innerData}
</React.Fragment>
)
})
}
</tbody>
Before v16
With the help of JSX syntactic sugar it is possible to return multiple elements from within a component, by writing them as comma separated elements in an array like
<tbody>
{
this.state.categories.map((category, index) => {
var innerData = this.state.data.map((row, i) => {
if (row.category === category) {
return (
<tr key={i}>
<td className="col-lg-2 text-center">{row.name}</td>
<td className="col-lg-2 text-center">{row.alias}</td>
<td className="col-lg-2 text-center">{row.description}</td>
<td className="col-lg-1 text-center">{row.default_value}</td>
<td className="col-lg-1 text-center">{row.min_value}</td>
<td className="col-lg-1 text-center">{row.max_value}</td>
<td className="col-lg-1 text-center">Action</td>
</tr>
)
}
return null
})
return ([
<th colSpan="7" key={index} style={{
'textAlign': 'left',
'paddingLeft': '5px',
'backgroundColor': '#D3D0CF'
}}>{this.state.category}</th>,
[...innerData]
])
})
}
</tbody>
Also when you make use of if statements within a map function, you need to have them outside of the return statement, now if you do {this.state.categories.map((category, index) => <tr>... it means that whatever is after the arrow is considered to be part of the return and hence you inner map's if statement will give you an error.
There is an issue on react github page for returning multiple elements. Read through it for more details.
Related
I have an HTML table like this:
<thead>
<tr>
<th scope="col">ID</th>
<th scope="col">Nouveaux FDES</th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
<tr v-for="row in rows">
<td> {{ row.details.id_inies }} </td>
<td> <a :href="row.url" target="_blank" rel="noreferrer noopener">{{ row.name }}</a></td>
<td style="text-align: center; vertical-align: middle;"> <input v-model="row.isSelected" type="checkbox"> </td>
</tr>
</tbody>
My goal is to get value in cells of the column ID and only the ones which are selected by user using the checkbox
I try something like this but it doesn't work:
{
const selectedFDES = this.rowsScraped.filter((fdes) => fdes.isSelected === true);
const idList = selectedFDES.reduce((acc, item) => {
acc[item.details.id_inies] = [];
return acc;
}, []);
console.log(idList);
this.$http.admin.putScrapedFDES(idList);
}
Your reducer function doesn't make much sense. It should probably look like this:
const selectedFDES = this.rowsScraped.filter(r => r.isSelected);
const idList = selectedFDES.reduce((acc, item) => {
acc.push(item.details.id_inies);
return acc;
}, []);
console.log(idList);
this.$http.admin.putScrapedFDES(idList);
For your case, I believe a .map() would be shorter, cleaner and more readable:
const idList = this.rowsScraped
.filter(r => r.isSelected)
.map(r => r.details.id_inies);
this.$http.admin.putScrapedFDES(idList);
See it working here:
new Vue({
el: '#app',
data: () => ({
rows: ['First', 'Second', 'Third']
.map((name, i) => ({
name: `${name} row`,
details: {
id_inies: i + 1,
},
url: '#',
isSelected: false
}))
}),
computed: {
selectedRows() {
return this.rows
.filter(row => row.isSelected)
.map(row => row.details.id_inies)
}
},
watch: {
selectedRows(newVal, oldVal) {
// This runs every time selectedRows changes value
console.log({ newVal, oldVal });
}
}
})
<script src="https://cdn.jsdelivr.net/npm/vue#2"></script>
<div id="app">
<table>
<thead>
<tr>
<th scope="col">ID</th>
<th scope="col">Nouveaux FDES</th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
<tr v-for="row in rows">
<td> {{ row.details.id_inies }} </td>
<td> <a :href="row.url" target="_blank" rel="noreferrer noopener">{{ row.name }}</a></td>
<td style="text-align: center; vertical-align: middle;"> <input v-model="row.isSelected" type="checkbox"> </td>
</tr>
</tbody>
</table>
<pre v-text="selectedRows" />
</div>
I'm getting data from my API, when I make a console.log or JSON.stringify in the API data it shows without problems but when I pass the data in a table with the map, simply nothing is presented in the table. .
const [users, setUsers] = useState([]);
const loadUser = () => {
getUsers().then(data => {
if(data.error) {
console.log(data.error)
}else{
setUsers(data)
}
})
}
const inforUsers = () => {
return(
<Fragment>
<table className="table table-bordered mb-5">
<thead className="thead-dark">
<tr>
<th scope="col">Id</th>
<th scope="col">Nome</th>
<th scope="col">Email</th>
<th scope="col">role</th>
<th scope="col">createdAt</th>
</tr>
</thead>
<tbody scope="row">
{Object.keys(users).map((values, key) => (
<tr key={key}>
<td>
{values._id}
</td>
<td>
{values.name}
</td>
<td>
{values.email}
</td>
<td>
{values.role === 1? 'Admin' : 'Simples User'}
</td>
<td>
{values.createdAt}
</td>
</tr>
))}
</tbody>
</table>
</Fragment>
)
}
I think you are confused about the data you have in hand. The key is the id for each object, so if you want that data, you should access the users object by each of the keys/ids you get from Object.keys. A brief example:
{Object.keys(users).map(id => (
{users[id]._id}
))}
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>
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} />
</>
I have one object array say defect and now if the status of the defect is open then it should show as button and it should read close the defect and if it is closed, then instead as button it should just mention as closed.
So, here statusRender is the issue and is now working as expected in the last column. Cant figure out what I am missing. Any leads?
render() {
if (defect.defect_status == 'open') {
statusRender = <button key={index} data-id={defect.id} onClick={() => this.showAlert(defect.defect_id)}>{defect.defect_status}</button>;
} else {
statusRender = { defect.defect_status };
}
return (
<div>
<table className="table table-bordered table-hover">
<thead>
<tr>
<th>Defect ID</th>
<th>Category</th>
<th>Description</th>
<th>Priority</th>
<th>Status</th>
</tr>
</thead>
<tbody>
{this.state.defectList.map((defect, index) => {
return (
<tr key={index}>
<td> {defect.defect_id} </td>
<td>{defect.defect_category}</td>
<td>{defect.defect_description}</td>
<td>{defect.defect_priority}</td>
<td> {statusRender}
</td>
</tr>
);
})
}
</tbody>
</table>
</div>
)
}
it is a scope issue you cannot declare defect outside of the map function
{this.state.defectList.map((defect,index) => {
return (
<tr key={index}>
<td> {defect.defect_id} </td>
<td>{defect.defect_category}</td>
<td>{ defect.defect_description}</td>
<td>{ defect.defect_priority}</td>
<td>
{
defect.defect_status === 'open'
? <button key={index} data-id={defect.id} onClick = {() => this.showAlert(defect.defect_id)}>{defect.defect_status}</button>;
: defect.defect_status;
}
</td>
</tr>
);
})
}
Thanks to user1095118 removing the semi colons did the job. I was missing the correctness of the curly braces which solved the issue
{
defect.defect_status == 'open'
?<button key={index} data-id={defect.id} onClick = {() => this.showAlert(defect.defect_id)}>{defect.defect_status}</button> : defect.defect_status
}
If you just need to access status string instead of the button maybe you should remove brackets in your if else statement
render() {
if(defect.defect_status=='open') {
statusRender = <button key={index} data-id={defect.id} onClick = {() => this.showAlert(defect.defect_id)}>{defect.defect_status}</button>;
} else {
// No brackets here ?
statusRender = defect.defect_status;
}
return (
<div>
<table className="table table-bordered table-hover">
<thead>
<tr>
<th>Defect ID</th>
<th>Category</th>
<th>Description</th>
<th>Priority</th>
<th>Status</th>
</tr>
</thead>
<tbody>
{this.state.defectList.map((defect,index) =>{
return(
<tr key={index}>
<td> {defect.defect_id} </td>
<td>{defect.defect_category}</td>
<td>{ defect.defect_description}</td>
<td>{ defect.defect_priority}</td>
<td> {statusRender}
</td>
</tr>
);
})
}
</tbody>
</table>
</div>
)
}
}