4

I have a simple React Component, and am trying to display a nested JSON object in render.

// React Component

class NodeDetail extends React.Component {
  constructor(props) {
    super(props);
    const node = {};

    this.state = {
      node
    }
  }

  componentDidMount() {
    Node.getNode(this.props.node_id).then((result) => {
      console.log(result.data);
      this.setState(() => ({node: result.data}));
    }).catch(function(error) {
      // TODO: handle error
    });
  }

  render() {
    return (
      <div>
        {this.state.node.node_status.name}
      </div>
    );
  }
};

export default NodeDetail;

This is the JSON(stored in result.data) getting returned from a rails API(some fields removed for brevity):

{  
   "id":1234,
   "name":"some-node",
   "created_at":"2018-05-18T15:23:24.012Z",
   "hostname":"some-host",
   "ip":"10.XXX.XXX.XXX",
   "mac":"24:6e:96:XX:11:XX",
   "node_status":{  
      "id":2,
      "name":"Parked"
   }
}

When I access the root level attributes in React with this.state.node.mac , it returns 24:6e:96:XX:11:XX.

When I try to access the name attribute in node_status using this.state.node.node_status.name, I get the the following error:

Uncaught TypeError: Cannot read property 'name' of undefined

I have also tried this.state.node['node_status'].name, and same error.

Why can't I access this object in the JSON, when clearly it is there?

4
  • What does this.state.node.node_status return? Commented May 18, 2018 at 19:21
  • @LGSon this.state.node.node_status returns undefined. But it is actually present. Verified this with console.log(result.data); Commented May 18, 2018 at 19:23
  • And as a test, what if you only grab the node_status, like this, this.setState(() => ({node: result.data.node_status})); and then do this.state.node.name Commented May 18, 2018 at 19:30
  • Given @user's answer below, when you tried this.state.node.mac (which you wrote worked), did you do that within the render method as well? Commented May 18, 2018 at 19:40

4 Answers 4

6

I bet it's because your call to the Rails API is asynchronous -- so your NodeDetail component tries to render before the API returns data / state is set with the result.data...try putting in a condition for non-existent node_status as some of the other answers have suggested.

So the current (wrong) data flow will be:

  1. constructor. state is set to {node: {}}
  2. componentDidMount. Calls API.
  3. render. Throws exception because this.state.node.node_status is undefined. Component breaks and won't render again...
  4. API returns. state is set to result.data. result.data gets logged to your console.

What you can do is something like:

render() {
    if (!this.state.node.node_status) {
      return null;
    }

    return (
      <div>
        {this.state.node.node_status.name}
      </div>
    );
  }

Or any of the other suggestions to account for the undefined value of this.state.node.node_status.

In general, you need to make sure that your render method will work, even with the default state values set in your constructor.

Sign up to request clarification or add additional context in comments.

8 Comments

It is an async call, and node_status is required(there is a DB constraint). If console.log(result.data); is showing node_status is present, i don't think this is a race condition.
It's not a "race condition", it's async Javascript...React will try to render your component before your API returns data, so it doesn't know that there is a DB constraint. It's basically guaranteed that rendering the component in the browser will be faster than your API...
With React, you need to make sure that render will always work, even if the method uses your default state values.
Then how do you explain that this.state.node.mac works, which is the immediate preceding object property of node_status?
Because you log this.state.node.mac after the API returns data. If you try to log this.state.node.mac in your render method, it will also fail....try it :-)
|
5

Error is coming correct Your are fetching data after component loaded In short you are calling your API in componentDidMount You can get rid off from this error in this way

 <div>
        {this.state.node.node_status?this.state.node.node_status.name:""}
 </div>

and I would suggest you to call fetch data API in componentWillMount or constructor which is the initial stage of component.

3 Comments

This resolved it, in my case I was doing {row.product_feed.id ? row.product_feed.id : "-"} and it was saying the id was undefined. Scale back the check to row.product_feed and boom it works.
Happy to hear this!.
was struggling with this myself and this worked for me. Thanks.
2

In render

newsapi_news.spacy_tags is an [object object]

newsapi_news.spacy_tags.all_tags THROWS can not read prop of undefined error

But this will work

data-tag={(newsapi_news.spacy_tags) ? newsapi_news.spacy_tags.all_tags : ""}

Code in render must be "compiled" or mounted or whatever the term is and the parent element is not defined yet but the parent of the parent is

1 Comment

This is perfect, did not think of it :)
0

Only solution that worked for me: https://stackoverflow.com/a/51175467

Issue is that JSON objects can be accessed one-level deep before they are populated, so it is not breaking your webpage until you try to access a nested attribute.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.