C33J4Y
C33J4Y

Reputation: 41

How do I display data from a JSON object using ReactJS?

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

Answers (1)

Drew Reese
Drew Reese

Reputation: 202605

Issue

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.

A Solution

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 => (....))
}

Update

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

Related Questions