React: Warning: validateDOMNesting(...): <table> cannot appear as a child of <tr> - javascript

I have some nested data, and i populated it in a table. but i got this error:
" cannot appear as a child of "
How can it be solved
{projects.map((project, key) => (
<tbody key={key}>
<tr><td rowSpan={project.receivers.length + 1}>{key}</td></tr>
{project.receivers.map((p, key) => (
<tr key={key}><td>{p.receiverName}</td>
{p.pages.map((n, k) => (
<table key={k}>
<tbody>
<tr><td>{n}</td></tr>
</tbody>
</table>
))}
<td>{project.title}</td>
<td>{project.message}</td>
</tr>
))}
</tbody>
))}

As the error message says <tr><table>...</table></tr> isn't allowed.
If a table needs to go inside another (which is a sensible data structure so infrequently I've only seen once (dubious) case of it in 25 years) then the <table> needs to go inside a <td> or <th>.
In this case your nested table only has one row and one column, so it makes no sense for it to be a table at all.
Replace:
{p.pages.map((n, k) => (
<table key={k}>
<tbody>
<tr><td>{n}</td></tr>
</tbody>
</table>
))}
With
{p.pages.map((n, k) => <td key={k}>{n}</td>)}

Related

Iterate over array in React

I want to iterate over an array that contains the end of a url so I can then 'concatenate' to the main site domain with the aim to gain a fully functional url (ie. www.mainUrlDomain.com/some-url)
This is my code:
<div>
<table>
<tbody>
<tr>
<th>Name</th>
<th>City</th>
<th>Code</th>
<th>Symbol</th>
</tr>
{data.map(data => (
<tr key={data.code}>
<td>
<Image
src={`${mainUrlDomain}/${data.code.toLowerCase()}.png`}
width={30}
height={20}
/>
</td>
{landingPagesKeys.includes(`${data.code}`)
?
<Link
href={`${mainUrlDomain}/${landingPages}`}
>
<td>
<a>{data.name}</a>
</td>
</Link> : <td>{data.name}</td>}
<td>{data.code}</td>
<td>{data.symbol}</td>
</tr>
))})
</tbody>
</table>
</div>
I have tried to add a way to iterate over the landingPages array like this:
{landingPagesKeys.includes(`${data.code}`)
?
{landingPages.map(data => (
<Link
href={`${mainUrlDomain}/${data`}
>
<td>
<a>{data.name}</a>
</td>
</Link> )} : <td>{data.name}</td>}
<td>{data.code}</td>
<td>{data.symbol}</td>
</tr>
))})
Unfortunately it didn't show me the array data as expected which contains the end of the desired url (ie. some-url) and landingPages after the url domain as the first example of code shows the entire array.
How to map through the array and obtain each individual url that landingPages contains?
You can use filter function to filter the data in an array.
landingPages.filter(data => landingPagesKeys.includes(`${data.code}`)).map(data => <div>...</div>)

Only one function getting result

I have this two funcions being called, but only the second one is showed on the table.
<table className="tableList tableList--space">
<thead>{this.tableHead()}</thead>
<tbody>
{this.state.products.map((item) =>
this.tableBody(item) && this.tableBodyComplements(item)
)}
</tbody>
</table>
Functions:
tableBody = (item) => (
<tr key={item.title}>
<td className="text-left">
{item.title}
</td>
<td className="text-left"> - </td>
<td className="text-right">R$ {item.price}</td>
<td className="text-center">{item.quantity}X</td>
<td className="text-right">R$ {item.total}</td>
<td className="text-left"></td>
</tr>
);
tableBodyComplements = (item) => (
<tr>
<td className="text-right">
{item.ComplementCategories.map(aux => {
return aux.Complements[0].title
})}
</td>
<td className="text-right"> - </td>
<td className="text-right"> - </td>
<td className="text-right">
{item.ComplementCategories.map(aux => {
return aux.Complements[0].quantity
})}
</td>
<td className="text-right">R$ {item.total}</td>
<td className="text-right"></td>
</tr>
);
Why is it only the second one is getting the desire result? I dont know if only the second one is being called or it is overlapping the first one in the table, How can I understand this better and fix it?
Your current implementation is performing what's called a "short-circuit", basically as long as the first value evaluates to a truthy value, the second will be returned, else it probably returns undefined.
To fix your implementation this is what it should look like:
<table className="tableList tableList--space">
<thead>{this.tableHead()}</thead>
<tbody>
{this.state.products.map((item) => (
<React.Fragment>
{this.tableBody(item)}
{this.tableBodyComplements(item)}
</React.Fragment>
)
)}
</tbody>
</table>
In javascript, any expression of the form
a && b
or
a || b
will only evaluate to either a or b. Never both. In fact it's not even clear what evaluating to "both a and b" would even mean in general.
In your case, you want to render both elements as JSX - so simply put them after each other, wrapping in a Fragment so that it's a legal React element:
{this.state.products.map((item) =>
<React.Fragment>
{this.tableBody(item)}
{this.tableBodyComplements(item)}
</React.Fragment
)}
The && operator actually doesn't return both. It returns the second argument if the first argument is true, otherwise it returns false. For example:
function Example() {
return (
<div>
{"Word1" && "Word2"}
</div>
) // this displays ONLY "Word2", since "Word1" is not a false value.
}
To fix this, wrap them in a fragment:
{this.state.products.map((item) =>
<React.Fragment>
{this.tableBody(item)}
{this.tableBodyComplements(item)}
</React.Fragment>
)}

Can not .map 2 different arrays in same table row, <tr>

I am trying to map an entry to the table. In that entry, there is a column which can have more than one value.In that case, a sub-row will be created in the same row. I have attached an example below image. Here is the code I have tried which messes the table up completely.
{intake.map((value) => {
return (
<tr>
<th className="text-center" scope="row">{value}</th>
</tr>
)
})}
{attendanceIds.map((val, i) => {
return (
<tr>
<td className="text-center">{date[i]}</td>
<td className="text-center">{duration[i]}</td>
<td className="text-center">{module[i]}</td>
<td className="text-center">{start[i]}</td>
<td className="text-center">{topic[i]}</td>
<td className="text-center">{studentsPresent[i]}</td>
<td className="text-center">{totalStudents[i]}</td>
<td className="text-center"><button className="button" id={val}>Details</button></td>
</tr>
)
})}
This is what I desire to get
This is what I get from the code above
This is the data I have. (One attendance ID has multiple intakes)
The data looks like it belongs on the same row semantically, so you shouldn't use a new row, you should add your multiple entries as e.g. div (or whatever suits) in your <td>. Then use CSS to style as required.
From your question, it isn't entirely clear what your data structure is in your component, but assuming your attendanceIds map in the way that your image shows, you can do something like this:
{attendanceIds.map((val, i) => {
return (
<tr>
<td className="text-center">{
val.intake.length === 1
? {val.intake[0]}
: val.intake.map(item=>
<div>{item}</div>)
}
}</td>
// add the rest of the <td>s here
</tr>
)
})}
(Note that I've left the rest of the mapping up to you as the way you've done it isn't clear to me.)

How can I display a clickable table from a nested array of objects?

I have an array of objects that contain some variable information, but also another array of objects with some more information.
I'm trying to display a table that shows the initial array, and when the user clicks on the row of that particular object, it would render a new table with the objects inside that specific array selected.
startCreateEventHandler = () => {
this.setState({ creating: true });
};
modalCancelHandler = () => {
this.setState({ creating: false });
};
render() {
return (
<div>
{this.state.creating && (
<Table>
<thead>
<tr>
<th>Details</th>
</tr>
</thead>
<tbody>
{this.props.archives.map(archive =>
archive.ticketRequests.map(request => (
<tr>
<td>{request.details}</td>
</tr>
))
)}
</tbody>
</Table>
)}
{this.state.creating ? null : (
<Table hover className="table table-striped">
<thead>
<tr>
<th>Title</th>
</tr>
</thead>
<tbody>
{this.props.archives.map(archive => (
<tr onClick={this.startCreateEventHandler}>
<td>{archive.title}</td>
</tr>
))}
</tbody>
</Table>
)}
</div>
);
}
What I get with this is the table set up correctly, but once I click on a row it displays all* rows objects in the next table instead of just that specific archive.
If I understood your problem then You also need to set the current clicked archive on the state and need to parse that selected archive
startCreateEventHandler = (archive) => {
this.setState({ creating: true, selectedArchive: archive });
};
modalCancelHandler = () => {
this.setState({ creating: false, selectedArchive: undefined });
};
render() {
return (
<div>
{this.state.creating && (
<Table>
<thead>
<tr>
<th>Details</th>
</tr>
</thead>
<tbody>
{this.state.selectedArchive.map(archive =>
<tr>
<td>{archive.details}</td>
</tr>
)}
</tbody>
</Table>
)}
{this.state.creating ? null : (
<Table hover className="table table-striped">
<thead>
<tr>
<th>Title</th>
</tr>
</thead>
<tbody>
{this.props.archives.map(archive => (
<tr onClick={(event)=>this.startCreateEventHandler(archive.ticketRequests)}>
<td>{archive.title}</td>
</tr>
))}
</tbody>
</Table>
)}
</div>
);
};

React-router: Using <Link> as clickable data table row

I'm new to using ReactJS and react-router. I want a clickable table row and something like the following setup:
<Link to=“#”>
<tr>
<td>{this.props.whatever1}</td>
<td>{this.props.whatever2}</td>
<td>{this.props.whatever3}</td>
</tr>
</Link>
but I know you can't put <a> tags between the <tbody> and <tr> tags. How else can I accomplish this?
PS: I prefer not to use jQuery if possible.
onClick works, but sometimes you need an actual <a> tag for various reasons:
Accessibility
Progressive enhancement (if script is throwing an error, links still work)
Ability to open a link in new tab
Ability to copy the link
Here's an example of a Td component that accepts to prop:
import React from 'react';
import { Link } from 'react-router-dom';
export default function Td({ children, to }) {
// Conditionally wrapping content into a link
const ContentTag = to ? Link : 'div';
return (
<td>
<ContentTag to={to}>{children}</ContentTag>
</td>
);
}
Then use the component like this:
const users = this.props.users.map((user) =>
<tr key={user.id}>
<Td to={`/users/${user.id}/edit`}>{user.name}</Td>
<Td to={`/users/${user.id}/edit`}>{user.email}</Td>
<Td to={`/users/${user.id}/edit`}>{user.username}</Td>
</tr>
);
Yes, you'll have to pass to prop multiple times, but at the same you have more control over the clickable areas and you may have other interactive elements in the table, like checkboxes.
Why don't you just use onClick?
var ReactTable = React.createClass({
handleClick: function(e) {
this.router.transitionTo('index');
},
render: function() {
return(
<div>
<table>
<thead>
<tr>
<th>Name</th>
<th>Age</th>
<th>Full Detail</th>
</tr>
</thead>
<tbody>
<tr onClick={this.handleClick.bind(this)}>
<td>{user.name}</td>
<td>{user.age}</td>
<td>{details}</td>
</tr>
</tbody>
</table>
</div>
);
}
});
This answers is based on #Igor Barbasin suggestion. This will add the link to the whole row instead of just the content and we also don't need to wrap all the individual 'td' element with 'Link'.
.table-row {
display: table-row
}
export default function Table() {
return (
<table>
<thead>
<tr>
<th>Name</th>
</tr>
</thead>
<tbody>
{/* treats the link element as a table row element */}
<Link className="table-row">
<td>Noname</td>
</Link>
</tbody>
</table>
)
}
You can use useHistory() hook:
Declare:
const history = useHistory();
and use it with <tr> or <td> tag:
<tr onClick={() => history.push("/yoururl")}>
<td>{this.props.whatever1}</td>
<td>{this.props.whatever2}</td>
<td>{this.props.whatever3}</td>
</tr>
{shipment.assets.map((i, index) => (
<tr
style={{ cursor: "pointer" }}
onClick={(e) => e.preventDefault()}
>
<td>{index + 1}</td>
<td>{i._id}</td>
<td>{i.status}</td>
<td></td>
</tr>
))}

Categories

Resources