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;
Related
I have a database of arrays that I want to unpack and insert into a table.
For example I'll have an array called a=[1,2,3,4] and b=['a','b','c','d'] each with and equal length.
And I will have a table with just the headers
a
b
My generated array would be
[[1,'a'],[2,'b'],[3,'c'],[4,'d']] created with the zip function from the underscore package.
My goal is to iterate over this array and generate the following
a
b
1
'a'
2
'b'
3
'c'
4
'd'
At the moment, I have a
function returnIt(){
let _ = require('underscore')
//returns [[1,'a'],[2,'b'],[3,'c'],[4,'d']] so x[0] = 1 and x[1] = 'a'
for (var x of _.zip([1,2,3,4],['a','b','c','d'])){
return (
<>
<td>
{x[0]}
</td>
<td>
{x[1]}
</td>
</>
)
}
return(
<table>
<thead>
<tr>
<th>a</th>
<th>b</th>
</tr>
</thead>
<tbody>
<tr>
{returnIt()}
</tr>
</tbody>
</table>
)
But this doesn't work. I get
As you can see I only get one row, the code does not produce more than one row! Sorry about the headers, I tried changing my program as much as I could to suit your eyes.
Anyways, how come this is my result and what can I change?
In returnIt, your for loop returns in the code block so it will only run once and return the first pair of array elements you've transformed to html elements. Try returning a mapping of the zipped elements to markup fragments. Then you’ll see them all.
Here's a complete example:
import _ from "underscore";
export default function App() {
return(
<table>
<thead>
<tr>
<th>a</th>
<th>b</th>
</tr>
</thead>
<tbody>
{returnIt()}
</tbody>
</table>)
}
function returnIt(){
//returns [[1,'a'],[2,'b'],[3,'c'],[4,'d']] so x[0] = 1 and x[1] = 'a'
let zipped = _.zip([1,2,3,4],['a','b','c','d'])
return zipped.map(pair => {
return (
<tr>
<td>
{pair[0]}
</td>
<td>
{pair[1]}
</td>
</tr>
)
})
}
Assuming this is using React
return (
<>
<td>
{x[0]}
</td>
<td>
{x[1]}
</td>
</>
)
<td> should be direct descendant of <tr>
The problem is you're returning from the loop body. So it'll only ever get to the first element.
for (anything) {
return ...
}
will always return immediately without continuing the loop, no matter what is between the parentheses.
You need to have the loop build up the HTML and return all the rows at once, or else maybe use something like a generator function and add a loop to the place that calls the function.
Assuming you're using some framework that lets you just return unquoted HTML like that, the problem with the table part of your return value is probably that you can't have a <div> between a <tr> and one of its enclosed <td>s.
I have a problem that seems simple but I'm not sure how to get around it. Basically, I have a React Bootstrap Table that renders table data coming in from an API. What I would like to do is to change a row color to green if a specific value in the data is greater than zero... here is an example:
const TableComponent = ({ fixtures }) => {
return (
<Table>
<tbody>
{fixtures.map((fixture) => (
<tr
key={fixture.id}
style={{
backgroundColor: 'green'
}}
>
<td> {fixture.value1} </td>
</tr>
))}
</tbody>
</Table>
);
};
So this defaults to a green backgroundColor for the row at all times. Is it possible to write a function so that, if fixture.value2 or fixture.value3 is greater than zero, the backgroundColor is green, but set to default otherwise?
It works for me.
Try like this.
const TableComponent = ({ fixtures }) => {
return (
<Table>
<tbody>
{fixtures.map((fixture) => (
<tr
key={fixture.id}
style={fixture.value2>0|| fixture.value3>0?{backgroundColor:'green'}:{}}
>
<td> {fixture.value1} </td>
</tr>
))}
</tbody>
</Table>
);
};
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 keep getting Each child in a list should have a unique "key" prop even though patient.timestamp and date is unique. obsHistory is an array and date is a string and date is only render once in a table row td. when I take out obsHistory.map or the other <tr key={date} one at a time the error still persists. I can't get the key prop error resolved not sure why.
renderObsHistory() {
const patientDetails = this.state.observations;
let groupedByDate = patientDetails.reduce((groupArray, patient) => {
groupArray[patient.date] = groupArray[patient.date] || [];
groupArray[patient.date].push(patient);
return groupArray;
}, {});
return Object.entries(groupedByDate).map(([date, obsHistory]) => (
<>
<tr key={date}>
<td>
<strong>{date}</strong>
</td>
</tr>
{obsHistory.map(patient => (
<tr key={patient.timestamp}>
<td>{patient.timestamp}</td>
</tr>
))}
</>
));
}
You should put the key on the Fragment (<>).
Like this :
<React.Fragment key={data}>
<tr>
/* ... */
</tr>
/* ... */
</React.Fragment>
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 />
];