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.
Related
I am pretty new to JS and React-Bootstrap and have read a few other questions related to this error. However, I am still unable to solve my problem. Thankful for any help you can give.
I am trying to implement a row that when clicked on will unhide another row beneath it with more detail about that row. I found this onclick handler from another question, but it was written like
onclickHandler = () => {}
which was giving me babel errors and I can't add that to my package
Here is my code simplified, didn't include the campaign objects, but just assume its a pretty simple json object
class Gateway extends React.Component {
onRowClickHandler(e){
const hiddenElement = e.currentTarget.nextSibling;
hiddenElement.className.indexOf("collapse show") > -1 ?
hiddenElement.classList.remove("show") : hiddenElement.classList.add("show");
}
render() {
function renderTable(element, index) {
return (
<tbody key={element.campaignId}>
<tr onClick={() => this.onRowClickHandler()}>
<td>{element.campaignId}</td>
<td>{element.contextualSignal.value}</td>
<td>{element.bid}</td>
<td>{element.retrievalScore}</td>
</tr>
<tr className="collapse">
<td colSpan="4">
Demo Content1
</td>
</tr>
</tbody>
)} // End of renderTable.
return (
<Table id="retrievalTable" striped bordered hover variant="dark">
<thead>
<tr>
<th>Campaign ID</th>
<th>Contextual Signal Value</th>
<th>Bid</th>
<th>Retrieval Score</th>
</tr>
</thead>
{this.state.campaigns.map(renderTable)}
</Table>
</div>
)}
}
export default Gateway
When I click on a row I get the error
Uncaught TypeError: Cannot read property 'onRowClickHandler' of undefined
the this reference is not referring to your react class but rather to the renderTable function, if you can move it to the class property and bind it using the react class constructor or to the render function
class Gateway extends React.Component {
onRowClickHandler(e){
const hiddenElement = e.currentTarget.nextSibling;
hiddenElement.className.indexOf("collapse show") > -1 ? hiddenElement.classList.remove("show") : hiddenElement.classList.add("show");
}
renderTable(element, index) {
return (
<tbody key={element.campaignId}>
<tr onClick={() => this.onRowClickHandler()}>
<td>{element.campaignId}</td>
<td>{element.contextualSignal.value}</td>
<td>{element.bid}</td>
<td>{element.retrievalScore}</td>
</tr>
<tr className="collapse">
<td colSpan="4">
Demo Content1
</td>
</tr>
</tbody>
)
}
render() {
return (
<Table id="retrievalTable" striped bordered hover variant="dark">
<thead>
<tr>
<th>Campaign ID</th>
<th>Contextual Signal Value</th>
<th>Bid</th>
<th>Retrieval Score</th>
</tr>
</thead>
{this.state.campaigns.map((e, i) => this.renderTable.bind(this, e, i))}
</Table>
</div>
)
}
}
export default Gateway
something like that, don't copy and paste., you probably know the next step
You have to be careful about the meaning of this in JSX callbacks. In JavaScript, class methods are not bound by default. If you forget to bind this.handlerFunction and pass it to onClick, this will be undefined when the function is actually called.
This is not React-specific behavior; it is a part of how functions work in JavaScript. Generally, if you refer to a method without () after it, such as onClick={this.handlerFunction}, you should bind that method.
Handling Events Reference
There are two ways to deal with this, first is to bind the function under constructor like:
constructor(props) {
super(props);
// This binding is necessary to make `this` work in the callback
this.onRowClickHandler= this.onRowClickHandler.bind(this);
}
or second is to create an arrow function like:
onRowClickHandler = () => {
// your logic here
}
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
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 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>
);
}