Reputation: 41
New programmer here learning ReactJS. I have some code that uses axios to make an HTTP request to get XMLData from a local file. Then on the response, I am using xml-js to turn that XML data into a JSON object. Then, I am taking that jsonObj
and saving it to a state using setState()
.
I have a function renderTableRows()
that is supposed to return JSX to display my JSON data on the browser. I destructured the state and try to console log from the renderTableRows()
but when I try to access users.result.body
I get
"TypeError: Cannot read property 'body' of undefined".
When I do it from the then()
within the componentDidMount()
I am able to access the data. I have also include an excerpt of the data I am reading at the bottom of the code.
I'd like to iterate using map()
through all the row array attributes. Any help would be appreciated.
class Table extends Component {
constructor(props) {
super(props)
this.state = {
users: []
}
}
async componentDidMount() {
axios.get(XMLData, {
"Content-Type": "application/xml; charset=utf-8"
})
.then((response) => {
var jsonObj = convert.xml2js(response.data,{compact:true, spaces: 4});
this.setState({users:jsonObj});
//console.log(this.state.users.result.body.row[0].col[0]);
});
}
renderTableHeader = () => {
return <th>
<td>Division Full Path</td>
<td>Billable Hours</td>
<td>Vacation Hours Only</td>
<td>Total Hours</td>
</th>
}
renderTableRows = () => {
const {users} = this.state
console.log(users.result.body);
return <h1>Hello from table rows</h1>
}
render() {
//const { users } = this.state
return <table>
<thead>
<tr>
{this.renderTableHeader()}
</tr>
</thead>
<tbody>
<tr>
{this.renderTableRows()}
</tr>
</tbody>
</table>
}
"header": {
"col": [
{
"label": {
"_text": "Counter Source Date"
}
},
{
"label": {
"_text": "Employee Id"
}
},
{
"label": {
"_text": "Counter Hours"
}
},
{
"label": {
"_text": " Division Full Path"
}
},
{
"label": {
"_text": " Projects/Equip/Vessels\nBillable"
}
},
{
"label": {
"_text": "Counter Name"
}
}
]
}
"body": {
"row": [
{
"col": [
{
"_text": "01/01/2021"
},
{
"_text": "2183"
},
{
"_text": "8.00"
},
{
"_text": "01 - Fort Lauderdale/Salvage"
},
{
"_text": "No"
},
{
"_text": "Holiday"
}
]
}
]
}
Upvotes: 4
Views: 450
Reputation: 202605
The initial state doesn't match how it is accessed in renderTableRows
.
this.state = {
users: []
}
Here this.state.users
is an array, so this.state.users.result
is undefined. This is fine until you then attempt to access a body
property and the error TypeError: Cannot read property 'body' of undefined
is thrown.
You can either start with valid initial state:
this.state = {
users: {
result: {}
}
}
Or use a bunch of guard clauses in renderTableRows
:
renderTableRows = () => {
const { users } = this.state
console.log(users.result && users.result.body);
return <h1>Hello from table rows</h1>
}
Or use Optional Chaining:
renderTableRows = () => {
const { users } = this.state
console.log(users.result?.body);
return <h1>Hello from table rows</h1>
}
Since you mention wanting to map through the rows the first option isn't what you want. If rendering rows it'll be something like:
renderTableRows = () => {
const {users} = this.state
return users.map(user => (....))
}
I suggest setting your state to jsonObj.result
properties, this is so you don't need to access the result
property each render, it just shortens the access. Map this.state.users.headerColumns
to the header columns and map this.state.rows
to each row and additionally map the row columns.
class Table extends Component {
constructor(props) {
super(props);
this.state = {
users: {
headerColumns: [],
rows: [],
}
};
}
async componentDidMount() {
axios
.get(XMLData, {
"Content-Type": "application/xml; charset=utf-8"
})
.then((response) => {
var jsonObj = convert.xml2js(response.data, {
compact: true,
spaces: 4
});
this.setState({ users: {
headerColumns: jsonObj.header.col,
rows: jsonObj.body.row
} });
});
}
renderTableHeader = () => {
const { users: { headerColumns } } = this.state;
return (
<th>
{headerColumns.map(col => (
<td key={col.label._text}>{col.label._text}</td>
))}
<td>Total Hours</td>
</th>
);
};
renderTableRows = () => {
const { users: { rows } } = this.state;
return rows.map((row, index) => {
let computedTotal;
return (
<tr key={index}>
{row.col.map((value, index) => {
// compute time total from row data
return (
<td key={index}>{value}</td>
);
})}
<td>{computedTotal}</td>
</tr>
)});
};
render() {
return (
<table>
<thead>
<tr>{this.renderTableHeader()}</tr>
</thead>
<tbody>
{this.renderTableRows()}
</tbody>
</table>
);
}
}
Upvotes: 4