I am getting the following warnings in a React component:
The related code is the following:
import React, { PropTypes } from 'react';
import { Checkbox } from 'react-bootstrap';
const MyComponent = (params) => {
function onSelect(event) {
params.onSelect(event, params.data.id);
}
return (
<tr key={params.data.id}>
{params.isSelectable ? (
<Checkbox onChange={onSelect}>
<td>{params.data.firstName} </td>
<td>{params.data.lastName} </td>
</Checkbox>
) : (
<div>
<td>{params.data.firstName} </td>
<td>{params.data.lastName} </td>
</div>
)}
</tr>
);
};
If I remove the div tags, I get the following error:
Adjacent JSX elements must be wrapped in an enclosing tag
I am new to React, so i am not quite clear on what is going on here. What's the problem and how can I fix it?
Update: my React version is 15.3.2.
If you need to return several elements and can't have a wrapper (such as in this case), you have a new option as of React 16.2. Fragments:
<React.Fragment>
<td>{params.data.firstName} </td>
<td>{params.data.lastName} </td>
</React.Fragment>
Or, you might be able to simplify it as:
<>
<td>{params.data.firstName} </td>
<td>{params.data.lastName} </td>
</>
The fragments won't resolve to any HTML element, so the <td>s will be direct children of your <tr>.
There are two problems with your code
Only td and th are allowed inside tr
In React version < 15, you have to wrap tags in one element, when you try to render them. In React 16, you can now do the following :
[
<td key={1}>{params.data.firstName} </td>,
<td key={2}>{params.data.lastName} </td>
]
instead of wrapping the tds inside a div
I also suggest you extract your logic outside of the tr
Related
I made an application in React that shows state holidays using state codes. Now I'm wondering how to handle error when a user types in a non-existent state code or more characters. Here's my try to do it.
import React from "react";
const Holidays = props => {
if (props.dataLoaded === false) {
return null;
} else {
return (
<div className="table-responsive">
<table>
<thead>
<tr>
<th>Holiday</th>
<th>Date</th>
<th>Type</th>
</tr>
</thead>
<tbody>
{props.country.map((country, index) =>
country && index === undefined ? (
<p>{props.error}</p>
) : (
<React.Fragment key={index}>
<tr>
<td className="holiday-name">
<strong>{country.name}</strong>
<br />
<p>{country.description}</p>
</td>
<td className="holiday-date">
{country.date.datetime.day}.{country.date.datetime.month}.
{country.date.datetime.year}
</td>
<td className="holiday-type">
{country.type[0]} {country.type[1]}
</td>
</tr>
</React.Fragment>
)
)}
</tbody>
</table>
</div>
);
}
};
export default Holidays;
But with this code when a non-existing state code or more characters is typed in, it throws a error "TypeError: Cannot read property 'map' of undefined".Any help is acceptable
The issue with your code is that your data props.country is being mapped before the data is defined, and as we know the map method .map() can only be used on arrays. Check here for more details. Therefore when you try use map on something that is undefined it will throw that error.
The reason that it is undefined at first is in most scenarios because you are fetching the data from the backend either from a server or from some public API. Before the operation of the data that is being fetched is complete your props.country will be undefined or whatever you initialize it to.
What you should do is change your current code of:
{props.country.map((country, index) =>
country && index === undefined ? (
<p>{props.error}</p>
) : (
<React.Fragment key={index}>
<tr>
<td className="holiday-name">
<strong>{country.name}</strong>
<br />
<p>{country.description}</p>
</td>
<td className="holiday-date">
{country.date.datetime.day}.{country.date.datetime.month}.
{country.date.datetime.year}
</td>
<td className="holiday-type">
{country.type[0]} {country.type[1]}
</td>
</tr>
</React.Fragment>
)
)}
To something like below, where we make a check of your props.country and make sure that it's loaded - once it is, then try use map on it to render out the contents.
{props.country ? props.country.map((country, index) =>
country && index === undefined ? (
<p>{props.error}</p>
) : (
<React.Fragment key={index}>
<tr>
<td className="holiday-name">
<strong>{country.name}</strong>
<br />
<p>{country.description}</p>
</td>
<td className="holiday-date">
{country.date.datetime.day}.{country.date.datetime.month}.
{country.date.datetime.year}
</td>
<td className="holiday-type">
{country.type[0]} {country.type[1]}
</td>
</tr>
</React.Fragment>
)
): <div>Data Loading!</div>}
So what we are doing here is adding another ternary conditional statement to check if props.country is true THEN apply the map method, else you simply return the div with contents "Data Loading!" before the data is loaded.
To clarify we are adding props.country ? right at the start of your code inside the opening curly brace { and : <div>Data Loading!</div> at the end right after the last closing bracket ) and before the closing curly brace }.
Now this <div>Data Loading!</div> could be anything you don't have to put a div or text you could simply put null, but before the data is fetched in those split seconds there will be empty space where your output would be.
Experiment with what you want to put there, but something nice would be to put a loader like a spinner or a styled message to indicate that your data is being fetched.
Edit:
You could also initialize your props.country to an array, so that using .map on it does not resolve into an error automatically even when it's empty. If this was passed down as a props from your parent component's state, simply do:
state = {
country: []
}
The prior methods to solving this problem are better, though. But you should still probably get used to initializing your state properties with the data that they will eventually hold.
import React from "react";
const Holidays = props => {
return (
<div className="table-responsive">
<table>
<thead>
<tr>
<th>Holiday</th>
<th>Date</th>
<th>Type</th>
</tr>
</thead>
<tbody>
{!props.country || props.country.length == 0 ? <p>{props.error}</p> : props.country.map((country, index) =>
<React.Fragment key={index}>
<tr>
<td className="holiday-name">
<strong>{country.name}</strong>
<br />
<p>{country.description}</p>
</td>
<td className="holiday-date">
{country.date.datetime.day}.{country.date.datetime.month}.
{country.date.datetime.year}
</td>
<td className="holiday-type">
{country.type[0]} {country.type[1]}
</td>
</tr>
</React.Fragment>
)}
</tbody>
</table>
</div>
);
};
export default Holidays;
I have a component called OrderItem that takes an object with multiple objects (at least two) inside it, and renders them as multiple rows inside a table. There will be multiple OrderItem components inside the table. The problem is that in the component's render function, I can't return multiple lines. I can only return a single component, and if I wrap them in a div, it says " <tr> cannot appear as a child of <div>"
The code looks something like this (I left some stuff out for easier readability)
Parent() {
render() {
return (
<table>
<tbody>
{
_.map(this.state.orderItems, (value, key) => {
return <OrderItem value={value} myKey={key}/>
})
}
</tbody>
</table>
)
}
}
class OrderItem extends React.Component {
render() {
return (
<div> // <-- problematic div
<tr key={this.props.myKey}>
<td> Table {this.props.value[0].table}</td>
<td> Item </td>
<td> Option </td>
</tr>
{this.props.value.map((item, index) => {
if (index > 0) { // skip the first element since it's already used above
return (
<tr key={this.props.myKey + index.toString()}>
<td><img src={item.image} alt={item.name} width="50"/> {item.name}</td>
<td>{item.selectedOption}</td>
</tr>
)
}
})}
</div>
)
}
}
Is there a way I can return those multiple rows and have them be in the same table without wrapping them in a div and getting an error? I realize I can make a separate table for each component, but that throws my formatting off a bit.
React 16 is now here to rescue, you can now use React.Fragment to render list of elements without wrapping it into a parent element. You can do something like this:
render() {
return (
<React.Fragment>
<tr>
...
</tr>
</React.Fragment>
);
}
Yes!! It is possible to map items to multiple table rows inside a table. A solution which doesn't throw console errors and semantically is actually correct, is to use a tbody element as the root component and fill with as many rows as required.
items.map(item => (
<tbody>
<tr>...</tr>
<tr>...</tr>
</tbody>
))
The following post deals with the ethical questions about it and explains why yes we can use multiple tbody elements
Can we have multiple <tbody> in same <table>?
One approach is to split OrderItem into two components, moving the rendering logic into a method Parent.renderOrderItems:
class Parent extends React.Component {
renderOrderItems() {
const rows = []
for (let orderItem of this.state.orderItems) {
const values = orderItem.value.slice(0)
const headerValue = values.shift()
rows.push(
<OrderItemHeaderRow table={headerValue.table} key={orderItem.key} />
)
values.forEach((item, index) => {
rows.push(
<OrderItemRow item={item} key={orderItem.key + index.toString()} />
)
})
}
return rows
}
render() {
return (
<table>
<tbody>
{ this.renderOrderItems() }
</tbody>
</table>
)
}
}
class OrderItemHeaderRow extends React.Component {
render() {
return (
<tr>
<td> Table {this.props.table}</td>
<td> Item </td>
<td> Option </td>
</tr>
)
}
}
class OrderItemRow extends React.Component {
render() {
const { item } = this.props
return (
<tr>
<td>
<img src={item.image} alt={item.name} width="50"/>
{item.name}
</td>
<td>
{item.selectedOption}
</td>
</tr>
)
}
}
It seems there is no way to wrap them cleanly, so the easier solution is to just put the whole table in the component and just have multiple tables and figure out the formatting.
Parent() {
render() {
return (
{_.map(this.state.orderItems, (value, key) => {
return <OrderItem value={value} myKey={key} key={key}/>
})}
)
}
}
class OrderItem extends React.Component {
render() {
return (
<table>
<tbody>
<tr>
<td> Table {this.props.value[0].table}</td>
<td> Item </td>
<td> Option </td>
</tr>
{this.props.value.map((item, index) => {
if (index > 0) { // skip the first element since it's already used above
return (
<tr key={this.props.myKey + index.toString()}>
<td> <img src={item.image} alt={item.name} width="50"/> {item.name}</td>
<td>{item.selectedOption}</td>
</tr>
)
}
})}
</tbody>
</table>
)
}
}
It is an old question, but maybe someone stumbles on it. Since I cannot comment yet, here is a little addition to the answer of #trevorgk:
I used this to render a table with multiple rows per item (about 1000 items resulting in about 2000 rows with 15 columns) and noticed really bad performance with Firefox (even in 57).
I had pure components rendering each item (one <body> per item containing two rows each) and each item contained a (controlled) checkbox.
When clicking the checkbox Firefox took more than ten seconds to update - although only one item was actually updated due to pure components. Chrome's update took at most half a second.
I switched to React 16 and I noticed no difference. Then I used the new AWESOME!!! feature of returning an array from a component's render function and got rid of the 1000 <tbody> elements. Chrome's performance was approximately the same while Firefox's "skyrocketed" to about half a second for an update (no perceived difference to Chrome)
In my case, the solution was to return an array instead of a fragment:
return [
<TrHeader />,
<TrRows />
];
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 :)
I'm starting out with ReactJS and I want to Unit Test it. I made a simple Component which renders an HTML td element:
...
render() {
return (
<td>{this.props.type == 'currency' ? '$' : ''}{this.props.content}</td>
);
}
...
I wrote a Jest Unit Test:
...
it('currency should prepend dollar sign', () => {
const datapointsTd = TestUtils.renderIntoDocument(
<DatapointsTd type="currency" content="123" />
);
const datapointsTdNode = ReactDOM.findDOMNode(datapointsTd);
expect(datapointsTdNode.textContent).toEqual('$123');
});
...
But it fails with the following message:
...
Warning: validateDOMNesting(...): <td> cannot appear as a child of <div>. See di
v > DatapointsTd > td.
FAIL __tests__\DatapointsTd-test.js (49.753s)
- DatapointsTd › it should display content in a td
- Invariant Violation: findComponentRoot(..., .0): Unable to find element. Thi
s probably means the DOM was unexpectedly mutated (e.g., by the browser), usuall
y due to forgetting a <tbody> when using tables, nesting tags like <form>, <p>,
or <a>, or using non-SVG elements in an <svg> parent. Try inspecting the child n
odes of the element with React ID ``.
at invariant (node_modules\react\node_modules\fbjs\lib\invariant.js:39:1
5)
...
I'm not sure what it means, I'm guessing that it tries to put the td element into a div element but then how do people Unit Test a Component like I'm trying to Unit Test?
You are right in guessing so. td must be a child of tr, which in turn must be a child of tbody or thead. The easy way out is to do something like this I guess
const datapointsTd = TestUtils.renderIntoDocument(
<table>
<tbody>
<tr>
<DatapointsTd type="currency" content="123" />
</tr>
</tbody>
</table>
);
Initial Error
I ran into a similar problem trying to test a table header component using React.TestUtils:
var header = TestUtils.renderIntoDocument(
<TableHeader text='Test' />
);
where TableHeader was something like this
class TableHeader extends React.Component {
render() {
return(<th>{this.props.text}</th>);
}
}
This led to the warning <th> cannot appear as a child of <div>.
Attempted Resolution
Attempting to use correct markup led to a new error.
var header = TestUtils.renderIntoDocument(
<table>
<thead>
<tr>
<TableHeader text='Test' />
</tr>
</thead>
</table>
);
The error here was Invariant Violation: findAllInRenderedTree(...): instance must be a composite component
Final Solution
Creating a wrapper component for the test worked for me.
class TestHeader extends React.Component {
render() {
return (
<table>
<thead>
<tr>
<TableHeader text={this.props.text} />
</tr>
</thead>
</table>
)
}
}
var header = TestUtils.renderIntoDocument(
<TestHeader text='Test' />
);
See https://stackoverflow.com/a/40210219/1951290 for the answer that helped me.
I am getting several Warnings on a Table with controls inside cells. If I click on a cell an event will occur. Some cells have a menu dropdown and an event depending on the selection. I am binding a value to the onClick event so that I can get a value in my function.
I am getting the following warnings on each of my rows:
Warning: bind(): React component methods may only be bound to the component instance.
My code looks like this:
items = this.state.currentValues.map(function(itemVal) {
return (
<tr>
<td onClick={this.rowClick.bind(this, itemVal.OrderId)}>{itemVal.Customer}</td>
<td onClick={this.rowClick.bind(this, itemVal.OrderId)}>{itemVal.OrderId}</td>
<td>
<div> Flag To Group </div>
<div>
<Menu label={itemVal.User}>
<List onSelect={this.assignResponsibleUser.bind(this, itemVal.User)}>
</List>
</Menu>
</div>
</td>
</tr>
);
}.bind(this)
);
return(
<Table selectable={true} scrollable={true} small={true}>
<tr>
{tableHeader}
</tr>
<tbody>
{items}
</tbody>
</Table>
);
I tried replacing:
onClick={this.rowClick.bind(this, itemVal.OrderId)}
with:
onClick={this.rowClick.bind(null, itemVal.OrderId)}
but it gives me the same warning.
I tried removing the .bind(this) but then it gives me an error because it cannot find the rowClick function.
How can I fix this issue?
Instead of using
this.array.map(function(){}.bind(this));
use
this.array.map(function(){}, this);
Its a map option for passing scope.
this.method mustbe a function such as:
senddel:function(id){
this.props.onCommentDel(id);//parent callback
},
render: function() {
return ( < div className = "commentList" >
{this.props.data.map(function(comment){
return (
<Comment key={comment.id} author={comment.author} onCommentDel={this.senddel.bind(this,comment.id)}>
{comment.text}
</Comment>
)
},this)}
< /div>
);
}