Cannot read property 'onRowClickHandler' of undefined - javascript

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
}

Related

ReactJS Sort Column Table localCompare throws erorr on column associated with ID

I am writing a react js Table with columns with sorting functionality. The columns with just text are working fine, but I am getting the following error:
TypeError: Cannot read property 'localeCompare' of undefined
I am assuming or guessing localeCompare only works with text column, it bombs on the columns with integers. correct?
My sort function is
SortTable(event, sortKey) {
if (!this.state.ascending) {
this.state.application.sort((a,b) => a[sortKey].localeCompare(b[sortKey]))
this.state.ascending = true
} else {
this.state.application.sort((a,b) => b[sortKey].localeCompare(a[sortKey]))
this.state.ascending = false
}
}
My table code is the following
<Table striped bordered>
<thead style={{'background-color': 'rgb(248,248,255)'}}>
<tr>
<th onClick={e=> this.sortTable(e,'myId')}>
<Button s
<div>Phase<i className={this.state.iconName}/></div></Button></th>
</tr>
</thead>
<tbody>
<tr>
<td style={{textAlign:'center'}}>{this.getStringBaseOnId(myId)}</td>
</tr>
</tbody>
</Table>
The function looks like
getStringBaseOnId(id) {
return 'Test'
}
Any help is appreciated. Thanks

Make the data selection from object to be dry

Quick question more on how should I approach this below to be dry. I have data which comes from the backend and on front i use react I have a component which is basically a table. Two api calls witch return different objects. I want to reuse one component rather than creating two separate tables as below. I pass an object of data to a table component, just need to know according to the object which keys to select.
<table>
<tbody>
<tr>
<td>{name}</td>
<td>{first_test.week_day}</td>
<td>{first.four.three}</td>
</tr>
</tbody>
</table>
<table>
<tbody>
<tr>
<td>{name}</td>
<td>{test.time}</td>
<td>{another.one.two}</td>
</tr>
</tbody>
</table>
two separate api requests example:
{
first: {four: {three: "different"}},
first_test: {week_day: 'Saturday'},
name: "first test"
}
{
another: {one: {two: "nice"}},
test: {time: 10:00},
name: "test"
}
so what would be a best way to approach this being dry without creating multiple components ? maybe some json schema?
It might be duplicate if someone drops the another related question would appreciate.
You conform whatever input to match your generic table component like so
function GenericTable({first, second, third}) {
return (
<table>
<tbody>
<tr>
<td>{first}</td>
<td>{second}</td>
<td>{third}</td>
</tr>
</tbody>
</table>
)
}
and call it like
<GenericTable first={name} second={first_test.week_day} third={first.four.three} />
or
<GenericTable first={name} second={test.time} third={another.one.two} />
update 1 based on comment
function GenericTable({ columns }) {
columnstb = columns.map((column, index) => (<td key={index}>{column}</td>);
return (
<table>
<tbody>
<tr>
{columnstb}
</tr>
</tbody>
</table>
)
}
and call it like
<GenericTable columns={[name, first_test.week_day, first.four.three]} />

How do I create a table from a JSON object in React JS?

What I want to do is use Propublica's nonprofit API to pull in information about various nonprofits and display certain attributes in a table.
Right now, I'm fetching an object:
const [ orgs, setOrgs ] = useState({})
const fetchOrgs = async () => {
const result = await Axios.get(`${API_URL}?state%5Bid%5D=${query}`)
setOrgs(result.data.organizations)
}
According to their API, organization objects are like this:
{
"organization":{
"id":142007220,
"ein":142007220,
"name":"PRO PUBLICA INC",
"careofname":null,
"address":"155 AVE AMERICA 13 FL",
"city":"NEW YORK",
"state":"NY",
"zipcode":"10013-0000",
"exemption_number":0,
"subsection_code":3,
"affiliation_code":3,
"classification_codes":"1000",
"ruling_date":"2008-02-01",
"deductibility_code":1,
"foundation_code":15,
"activity_codes":"0",
"organization_code":1,
"exempt_organization_status_code":1,
"tax_period":"2018-12-01",
"asset_code":8,
"income_code":8,
"filing_requirement_code":1,
"pf_filing_requirement_code":0,
"accounting_period":12,
"asset_amount":40988939,
"income_amount":27237842,
"revenue_amount":26685933,
"ntee_code":"A20",
"sort_name":null,
"created_at":"2020-04-13T21:42:55.607Z",
"updated_at":"2020-04-13T21:42:55.607Z",
"data_source":null,
"have_extracts":null,
"have_pdfs":null
},
This is my current function, which is not working:
const displayTable = () => {
return (
<Table striped border='true' hover='true' responsive='true'>
<thead>
<tr>
<th>#</th>
<th>Name</th>
<th>State</th>
<th>City</th>
<th>NTEE</th>
<th>Income</th>
</tr>
</thead>
<tbody>
<tr>
<td>0</td>
<td>Test</td>
<td>Test</td>
<td>Test</td>
<td>Test</td>
<td><a href=' '>Link</a></td>
</tr>
{Object.keys(orgs).map(({name, state, ntee_code, income_amount}, i) => (
<tr key={i}>
<td>{i + 1}</td>
<td>{name}</td>
<td>{state}</td>
<td>{ntee_code}</td>
<td>{income_amount}</td>
</tr>
))}
</tbody>
</Table>
)
}
When I run this, I get the test row, and then many rows of empty cells. Only the "#" column has anything, since it's just the index value. What am I doing wrong here? Any help would be much appreciated, I've only just started learning React and JS.
here is a working example:
return (
<div>
<h1>Organizations:</h1>
<table style={{ width: '100%', textAlign: 'center', border: 'solid 1px' }}>
<thead>
<th>Index</th><th>Name</th><th>State</th><th>ntee_code</th><th>income_amount</th>
</thead>
<tbody>
{orgs.map((organization, i) => {
const org = organization.organization;
return (
<tr>
<td>{i}</td><td>{org.name}</td><td>{org.state}</td><td>{org.ntee_code}</td><td>{org.income_amount}</td>
</tr>
)
})}
</tbody>
</table>
</div>
)
I'm posting just the returned markup as I understood you have no problem with fetching the data, and you just don't know how to display.
Your solution had the right idea of iterating over the organization array to create <tr>s. But you didn't need the Object.keys() to access the values. You first need to iterate over the objects in the array, and then drill down each object using the . syntax: const org = organization.organization; and then e.g org.name.
Note that each of your objects actually has only one field, i.e organization, so you first need to drill into it and only then you can get the values further down the object tree. If you want to deconstruct the object in map() you can, and again you had the right idea about how it's done, you just got the layer wrong. So you can do
{orgs.map(({organization}, i) => {
const { name, state, ntee_code, income_amount } = organization;
return (
<tr>
<td>{i}</td><td>{name}</td><td>{state}</td><td>{ntee_code}</td><td>{income_amount}</td>
</tr>
)
})}
I hope it can help and it's all clear.
Good luck!

Ternary operator in map() react

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;

How do I wrap a React component that returns multiple table rows and avoid the "<tr> cannot appear as a child of <div>" error?

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 />
];

Categories