I am trying to display the values inside a state object:
I have tried two methods:
Method 1:
<tbody>
{Object.keys(this.state.operationSavInformation).map(
function(key, index) {
return (
<tr key={key}>
<td>{`${key}:`}</td>
<td>
<strong>
{this.state.operationSavInformation[key]}
</strong>
</td>
</tr>
);
}
)}
</tbody>
Method 2:
<tbody>
{Object.keys(this.state.operationSavInformation).forEach(
function(key) {
var value = this.state.operationSavInformation[key];
return (
<tr key={key}>
<td>{`${key}:`}</td>
<td>
<strong>value</strong>
</td>
</tr>
);
}
)}
</tbody>
Both give this error:
TypeError: Cannot read property 'state' of undefined
Unfortunately, these are the only two ways I've found to map over an object values in javaScript. This would work if the object was defined normally. However the problem here is that this is not accessible inside map or forEach, that's why I get the error.
Just grab a reference to it outside the predicate function, like this:
const data = this.state.operationSavInformation
Object.keys(data).map(key => (<tr key={key}>
<td>{`${key}:`}</td>
<td>
<strong>
{data[key]}
</strong>
</td>
</tr>
))
Related
I'm currently building a budgeting application with React and JavaScript. Right now I'm able to print a table of costs like shown below.
Name
Budget
Used $
Used %
Available
Food
300
300
100
0
Streaming services
600
600
100
0
Here is the code where I fetch data and map through each item to the table:
<tbody>
{expenses.budgetedAmounts
.sort((a, b) => {
return a.department < b.department;
})
.map((item, index) => (
<tr
key={index}
>
<td>
{item.name}
</td>
<td>
{item.budgetedExpense}
</td>
<td>
{item.actualExpense}
</td>
<td>
{item.percentageUsed}
</td>
<td>
{item.availableExpense}
</td>
</tr>
))}
</tbody>
I would also like to add sub-items to the table that show what the budget of each item consist of. For example, the budget set for food consists of french fries and apples.
Name
Budget
Used $
Used %
Available
Food
300
300
100
0
• French fries
150
• Apples
150
Streaming services
600
600
100
0
• Netflix
300
• Disney+
300
Subitems are also in array which can be accessed via "item". I'm thinking that I could just add another loop after the first map method:
{item.nestedItems.map((nested, secondIndex) =>
)}
And I could access items like this:
{nested.name} and {nested.price}
Data is fetched correctly, but I can't seem to figure out where to place nested loop in the code and how to achieve wanted table structure.
I know this is probably hard question to answer without being able to try the code, but any ideas to help would be just great!
You could first flatten your array, and then do that final map, taking some precautions that undefined properties do not result in undefined outputs:
<tbody>
{expenses.budgetedAmounts
.sort((a, b) => a.department < b.department)
.flatMap(a => [a, ...a.nestedItems])
.map((item, index) => (
<tr key={index}>
<td>{item.name}</td>
<td>{item.budgetedExpense ?? item.price}</td>
<td>{item.actualExpense ?? ""}</td>
<td>{item.percentageUsed ?? ""}</td>
<td>{item.availableExpense ?? ""}</td>
</tr>
))}
</tbody>
Note that the index now no longer corresponds with the original index in the non-flattened array. I assumed it just served as a unique reference, but if not, you may want to add some code to still have access to the original index.
The simplest way to do this do exactly what you want to. Write another map right after the tag in the first map to render sub-item rows. Something like this:
<tbody>
{expenses.budgetedAmounts
.sort((a, b) => {
return a.department < b.department;
})
.map((item, index) => (
<><tr
key={index}
>
<td>
{item.name}
</td>
<td>
{item.budgetedExpense}
</td>
<td>
{item.actualExpense}
</td>
<td>
{item.percentageUsed}
</td>
<td>
{item.availableExpense}
</td>
</tr>
{item.nestedItems.map((nested, secondIndex) => <tr>
<td> {nested.name}</td> <td> {nested.price} </td>
</tr>)}</>
))}
</tbody>
Try this
<tbody>
{ budgetedAmounts.map((item,index)=>{
return(
<>
<tr key={index + "b"}>
<td>{item.name}</td>
<td>{item.budgetedExpense}</td>
<td>{item.actualExpense}</td>
<td>{item.percentageUsed}</td>
<td>{item.availableExpense}</td>
</tr>
{ item.subItem && item.subItem.map((subItem,subIndex)=>{
return(
<tr key={item.id + " __ "+subIndex}>
<td>{subItem.name}</td>
<td>{subItem.budgetedExpense}</td>
<td>{subItem.actualExpense}</td>
<td>{subItem.percentageUsed}</td>
<td>{subItem.availableExpense}</td>
</tr>
)
})
}
</>
)
})
}
</tbody>
I'm not sure if this is the right way to push table element in React, but I've been doing this all the time. So this for loop below will get executed everytime re-render occurs or in this case when I change my input. what if I have like 100 Input?
Questions:
Is this the right way to draw table element?
wouldn't re-render cause bad performance especially if you have some kind of loop before return?
If this is not the right way to do it. Please show me the right way to do it.
function App() {
const [inputs,setInputs] = useState({input1:'',input2:'',input3:''})
function handleOnChange(e){
const {name,value} = e.target
setInputs(prev => ({...prev, [name]:value}))
}
let tableElement = []
for(let i = 1; i <= 3; i++){
tableElement.push(<tr key={i}>
<td><Input value={inputs[`${i}`]} name={`input${i}`} onChange={handleOnChange} /></td>
</tr>)
}
return (
<div>
<Table>
<tbody>
{tableElement}
</tbody>
</Table>
</div>
);
}
export default App;
Given code:
let tableElement = []
for (let i = 1; i <= 3; i++){
tableElement.push((
<tr key={i}>
<td>
<Input value={inputs[`${i}`]} name={`input${i}`} onChange={handleOnChange} />
</td>
</tr>
))
}
return (
<div>
<Table>
<tbody>
{tableElement}
</tbody>
</Table>
</div>
);
Questions:
Is this the right way to draw table element?
It isn't wrong, though it may not be the most common way to render out an array to JSX. Typically you may opt to map an array to JSX
const tableElement = [...Array(3).keys()].map((i) => (
<tr key={i}>
<td>
<Input value={inputs[`${i}`]} name={`input${i}`} onChange={handleOnChange} />
</td>
</tr>
));
return (
<div>
<Table>
<tbody>
{tableElement}
</tbody>
</Table>
</div>
);
Or directly in the JSX
return (
<div>
<Table>
<tbody>
{[...Array(3).keys()].map((i) => (
<tr key={i}>
<td>
<Input
value={inputs[`${i}`]}
name={`input${i}`}
onChange={handleOnChange}
/>
</td>
</tr>
))}
</tbody>
</Table>
</div>
);
wouldn't re-render cause bad performance especially if you have some kind of loop before return?
I suppose there is a potential for poor performance, but due to the nature of the UI needing to map out a bunch of array elements when the component renders this is work that would be necessary anyway.
React is already pretty optimized out-of-the-box. It uses a reconciliation process to determine what mapped elements actually changed from the previous render and only rerenders what is necessary. The React key is used when mapping arrays to help in this regard.
If this is not the right way to do it. Please show me the right way to do it.
I shared the more common methods of mapping an array to JSX in point #1 above.
I'm trying to display data in the table, but for some reasons appears empty fields. I think the issue is that firstly is looking deaths - fills the elements and cases becomes empty. Tried to do in many different ways. In some ways all data are just pulled in one column, in other ways the data just go in rows. Tryied to use lodash as well.
Current code:
<table className="table">
<tr>
<th>Cases</th>
<th>Deaths</th>
</tr>
{_.map(countryData, (item, key) => {
if (item.indicator === "cases") {
return (
<tr>
<td>{item.weekly_count}</td>
<td></td>
</tr>
);
} else {
return (
<tr>
<td>{item.weekly_count}</td>
</tr>
);
}
})}
</table>
Also tried in this way(still empty fields):
{countryData.map((value, key) => {
return (
<tr>
<td>
{value.indicator === "cases" ? value.weekly_count : null}
</td>
<td>
{value.indicator === "deaths" ? value.weekly_count : null}
</td>
</tr>
);
})}
My data from console.log:
Data
Expected result: expected result
Example of issue: Issue
countryData: This data comes after selecting the specific country
The map function is working just fine. The problem is with the object countryData. The object currently does not contain an array. Please make sure the object looks like:
countryData = [{indicator:"cases", weekly_count: xx}, {indicator:"deaths", weekly_count: yy}.....]
I have only used two fields inside each object, you may add more objects according to your requirement.
In order to view cases and deaths as separate columns, please add an empty cell before displaying "deaths".
{_.map(countryData, (item, key) => {
if (item.indicator === "cases") {
return (
<tr>
<td>{item.weekly_count}</td>
<td></td>
</tr>
);
} else {
return (
<tr>
<td></td>
<td>{item.weekly_count}</td>
</tr>
);
}
})}
I need to render <tr> start and close it, based on a condition:
{grid.map((item, index) => {
return <React.Fragment key={index}>
{item.x === 0 ? <tr/> : ""}
<td id={`td_${item.x}_${item.y}`}>
{item.name}
</td>
</React.Fragment>
})}
Above code rendering all <td> tag outside of <tr> tag, but I want all <td> in <tr> tag.
In this code, I want <tr> start when item.x === 0 and need to render </tr> end in bottom this loop.
{grid.map((item, index) => {
return <React.Fragment key={index}>
{item.x === 0 ? <tr> : ""}
<td id={`td_${item.x}_${item.y}`}>
{item.name}
</td>
{item.x === 0 ? </tr> : ""}
</React.Fragment>
})}
This code showing error in jsx syntax error.
React component trees aren't HTML strings, they're object trees. You need to have a tr object, provide it with a bunch of td children, and then switch to a new tr object and provide its children to it.
Ideally, change how you're storing grid so that it contains the rows, with child objects/arrays for the cells.
If you can't do that, you can do it on the fly:
const rows = [];
let children = [];
for (const item of grid) {
if (grid.x === 0 && children.length) {
rows.push(children);
children = [];
}
children.push(item);
}
if (children.length) {
rows.push(children);
}
Then to render:
{rows.map((children, index) => (
<tr key={index}>
{children.map(item =>
<td id={`td_${item.x}_${item.y}`}>
{item.name}
</td>
)}
</tr>
))}
You'll want to double-check that for minor typos, etc., but it's the fundamental idea: Build an array of rows, with arrays of cells inside.
I am rendering table data in React JS and am having trouble getting my table row to go hidden on click of a child "delete" button. My current handler and render functions looks like this:
...
changeHandler: function(e) {
...
},
deleteHandler: function(e) {
e.currentTarget.closest("tr").style.visibility = "hidden";
},
render: function() {
return (
<div>
<div class="container">
<select onChange={ this.changeHandler.bind(this) }>
<option></option>
...
</select>
<table>
<thead>
<tr>
...
</tr>
</thead>
<tbody>
{data.map(function(row, j) {
return <tr key={j}>
<td>{row.text}</td>
<td><a href="" onClick={this.deleteHandler.bind(this, j)}>delete</a></td>
</tr>
}
)}
</tbody>
</table>
</div>
</div>
);
}
...
When I click on the delete anchor, I get this error in the console:
Uncaught TypeError: Cannot read property 'bind' of undefined
I don't understand why my delete handler isn't being recognized and bound, when the changeHandler I'm using is. Could somebody please tell me how to get this event to hit the handler and how to target the parent tr to have it hidden?
After the correction in the typo above, I see that was not the error here. Check out the following fiddle to see it in action. There will be some need of style changes when you hide the row.
https://jsfiddle.net/vgo52rey/
The problem is with the binding of 'this' in the filter function in the fiddle. Abstract that out into another method so you can store the reference to this in a different variable, then you can keep the reference to this.delete or this.deleteHandler.
delete: function(e) {
e.currentTarget.closest("tr").style.visibility = "hidden";
},
renderRows: function() {
var shouldIRender =(row) => (this.state.filter === row.status || this.state.filter === "");
var self = this
return requests.filter(shouldIRender).map(function(row, j) {
return <tr key={j}>
<td style={tdStyle}>{row.title}</td>
<td style={tdStyle}>{row.status}</td>
<td style={tdStyle}>{row.created_at}</td>
<td style={tdStyle}>{row.updated_at}</td>
<td style={tdStyle}><a href="#" onClick={self.delete}>delete</a></td>
</tr>
}
)
},
in your render method now you can just supply this renderRows method return value:
<tbody>
{this.renderRows()}
</tbody>