0

UPDATE I can access everything just fine in the return statement of render() function. F.e user.exercises[0].exercise.name outputs "squats". That's why it's even more confusing why I can't access it before return statement to create a function mapping the values to a table. UPDATE

I seem to have problems with accessing specific keys in React. The object that I try to read is requested from backend API from mongodb and outputted as JSON. I've already tried accessing it after mapping the object to an array through lodash, but the issue is the same. I am probably just reading the object wrong and specifying the wrong key, but I cannot see it. I have no problems console.logging the whole JSON or the top keys, f.e "name", or "age" by writing {user.name}, but anything with more depth and it crashes. Here's the JSON:

{
    "_id": "592ab4523a4d39085fe4c1d9",
    "nickname": "mmsmsy",
    "name": "Mateusz",
    "gender": "male",
    "age": 26,
    "exercises": [
      {
        "exercise": {
          "name": "squats",
          "records": []
        }
      },
      {
        "exercise": {
          "name": "legpresses",
          "records": []
        }
      },
      {
        "exercise": {
          "name": "deadlifts",
          "records": []
        }
      },
      {
        "exercise": {
          "name": "benchpresses",
          "records": []
        }
      },
      {
        "exercise": {
          "name": "pullups",
          "records": []
        }
      },
      {
        "exercise": {
          "name": "shoulderpresses",
          "records": []
        }
      },
      {
        "exercise": {
          "name": "curls",
          "records": []
        }
      }
    ]
  }

And the React code:

import React, { Component } from 'react';
import { Link } from 'react-router-dom';
import axios from 'axios';
import _ from 'lodash';

class UserDetails extends Component{
  constructor(props) {
    super(props)
    this.state = {
      user: null,
      loading: false
    }
  }
  componentDidMount() {
    this.setState({
      loading: true
    });
    axios.get(`http://192.168.0.248:3001/api/v1/users/${this.props.match.params.id}`)
      .then(res => res.data)
      .then(user => {
        this.setState({
          user: user,
          loading: false
        });
      });
  }
  render() {
    const {loading, user} = this.state;
    let userInfo = _.map(user, (value, prop) => {
        return { "prop": prop, "value": value };
      });
    console.log(user, userInfo);
    if (loading || !user) {
      return (
        <p className="user-loading">Loading ...</p>
      );
    }
    return (
      <div id="user-details">
        <div className="nav">
          <Link className="nav-back-to-list" to="/">Back to the list</Link>
        </div>
        <div id="user-details-icon">
          <img src={`/images/user_${user.gender}.png`} alt={`generic user ${user.gender} icon`} />
        </div>
        <table>
          <tbody><tr><td>Nickname</td><td>{user.nickname}</td></tr></tbody>
          <tbody><tr><td>Name</td><td>{user.name}</td></tr></tbody>
          <tbody><tr><td>Gender</td><td>{user.gender}</td></tr></tbody>
          <tbody><tr><td>Age</td><td>{user.age}</td></tr></tbody>
        </table>
        <h1>Records</h1>
      </div>
    )
  }
}

export default UserDetails;

Here's what I get without error with console.log(user);

Object {_id: "592ab4523a4d39085fe4c1d9", nickname: "mmsmsy", name: "Mateusz", gender: "male", age: 26…}age: 26exercises: Array(7)0: Objectexercise: Objectname: "squats"records: Array(0)length: 0__proto__: Array(0)__proto__: Objectconstructor: function Object()hasOwnProperty: function hasOwnProperty()isPrototypeOf: function isPrototypeOf()propertyIsEnumerable: function propertyIsEnumerable()toLocaleString: function toLocaleString()toString: function toString()valueOf: function valueOf()__defineGetter__: function __defineGetter__()__defineSetter__: function __defineSetter__()__lookupGetter__: function __lookupGetter__()__lookupSetter__: function __lookupSetter__()get __proto__: function __proto__()set __proto__: function __proto__()__proto__: Object1: Objectexercise: Object__proto__: Object2: Object3: Object4: Object5: Object6: Objectlength: 7__proto__: Array(0)gender: "male"name: "Mateusz"nickname: "mmsmsy"_id: "592ab4523a4d39085fe4c1d9"__proto__: Object

and here's after converting it with lodash to an array console.log(userInfo);

(6) [Object, Object, Object, Object, Object, Object]

Now, the example and the error that I get if I try to access f.e: console.log(user.exercises[0].exercise.name);

Uncaught TypeError: Cannot read property 'exercises' of null
    at UserDetails.render (UserDetails.js:33)
    at ReactCompositeComponent.js:796
    at measureLifeCyclePerf (ReactCompositeComponent.js:75)
    at ReactCompositeComponentWrapper._renderValidatedComponentWithoutOwnerOrContext (ReactCompositeComponent.js:795)
    at ReactCompositeComponentWrapper._renderValidatedComponent (ReactCompositeComponent.js:822)
    at ReactCompositeComponentWrapper.performInitialMount (ReactCompositeComponent.js:362)
    at ReactCompositeComponentWrapper.mountComponent (ReactCompositeComponent.js:258)
    at Object.mountComponent (ReactReconciler.js:46)
    at ReactCompositeComponentWrapper.performInitialMount (ReactCompositeComponent.js:371)
    at ReactCompositeComponentWrapper.mountComponent (ReactCompositeComponent.js:258)
    at Object.mountComponent (ReactReconciler.js:46)
    at ReactDOMComponent.mountChildren (ReactMultiChild.js:238)
    at ReactDOMComponent._createInitialChildren (ReactDOMComponent.js:697)
    at ReactDOMComponent.mountComponent (ReactDOMComponent.js:516)
    at Object.mountComponent (ReactReconciler.js:46)
    at ReactCompositeComponentWrapper.performInitialMount (ReactCompositeComponent.js:371)
    at ReactCompositeComponentWrapper.mountComponent (ReactCompositeComponent.js:258)
    at Object.mountComponent (ReactReconciler.js:46)
    at ReactCompositeComponentWrapper.performInitialMount (ReactCompositeComponent.js:371)
    at ReactCompositeComponentWrapper.mountComponent (ReactCompositeComponent.js:258)
    at Object.mountComponent (ReactReconciler.js:46)
    at ReactCompositeComponentWrapper.performInitialMount (ReactCompositeComponent.js:371)
    at ReactCompositeComponentWrapper.mountComponent (ReactCompositeComponent.js:258)
    at Object.mountComponent (ReactReconciler.js:46)
    at mountComponentIntoNode (ReactMount.js:104)
    at ReactReconcileTransaction.perform (Transaction.js:140)
    at batchedMountComponentIntoNode (ReactMount.js:126)
    at ReactDefaultBatchingStrategyTransaction.perform (Transaction.js:140)
    at Object.batchedUpdates (ReactDefaultBatchingStrategy.js:62)
    at Object.batchedUpdates (ReactUpdates.js:97)
    at Object._renderNewRootComponent (ReactMount.js:320)
    at Object._renderSubtreeIntoContainer (ReactMount.js:401)
    at Object.render (ReactMount.js:422)
    at Object.<anonymous> (index.js:10)
    at __webpack_require__ (bootstrap 9a6d4f1…:657)
    at fn (bootstrap 9a6d4f1…:85)
    at Object.<anonymous> (fetch.js:461)
    at __webpack_require__ (bootstrap 9a6d4f1…:657)
    at validateFormat (bootstrap 9a6d4f1…:706)
    at bundle.js:710
render @ UserDetails.js:33
(anonymous) @ ReactCompositeComponent.js:796
measureLifeCyclePerf @ ReactCompositeComponent.js:75
_renderValidatedComponentWithoutOwnerOrContext @ ReactCompositeComponent.js:795
_renderValidatedComponent @ ReactCompositeComponent.js:822
performInitialMount @ ReactCompositeComponent.js:362
mountComponent @ ReactCompositeComponent.js:258
mountComponent @ ReactReconciler.js:46
performInitialMount @ ReactCompositeComponent.js:371
mountComponent @ ReactCompositeComponent.js:258
mountComponent @ ReactReconciler.js:46
mountChildren @ ReactMultiChild.js:238
_createInitialChildren @ ReactDOMComponent.js:697
mountComponent @ ReactDOMComponent.js:516
mountComponent @ ReactReconciler.js:46
performInitialMount @ ReactCompositeComponent.js:371
mountComponent @ ReactCompositeComponent.js:258
mountComponent @ ReactReconciler.js:46
performInitialMount @ ReactCompositeComponent.js:371
mountComponent @ ReactCompositeComponent.js:258
mountComponent @ ReactReconciler.js:46
performInitialMount @ ReactCompositeComponent.js:371
mountComponent @ ReactCompositeComponent.js:258
mountComponent @ ReactReconciler.js:46
mountComponentIntoNode @ ReactMount.js:104
perform @ Transaction.js:140
batchedMountComponentIntoNode @ ReactMount.js:126
perform @ Transaction.js:140
batchedUpdates @ ReactDefaultBatchingStrategy.js:62
batchedUpdates @ ReactUpdates.js:97
_renderNewRootComponent @ ReactMount.js:320
_renderSubtreeIntoContainer @ ReactMount.js:401
render @ ReactMount.js:422
(anonymous) @ index.js:10
__webpack_require__ @ bootstrap 9a6d4f1…:657
fn @ bootstrap 9a6d4f1…:85
(anonymous) @ fetch.js:461
__webpack_require__ @ bootstrap 9a6d4f1…:657
validateFormat @ bootstrap 9a6d4f1…:706
(anonymous) @ bundle.js:710

4 Answers 4

1

Problem is that you have assigned initial state user to be null and when you log user.exercises[0] it throws you that error since the result of the API won't be ready yet when the app was rendered first time.

Perform a check before console.log()

class UserDetails extends Component{
  constructor(props) {
    super(props)
    this.state = {
      user: null,
      loading: false
    }
  }
  componentDidMount() {
    this.setState({
      loading: true
    });
    axios.get(`http://192.168.0.248:3001/api/v1/users/${this.props.match.params.id}`)
      .then(res => res.data)
      .then(user => {
        this.setState({
          user: user,
          loading: false
        });
      });
  }
  render() {
    const {loading, user} = this.state;
    if(user != null) {
      let userInfo = _.map(user, (value, prop) => {
        return { "prop": prop, "value": value };
      });

    console.log(user.exercises[0].exercise, userInfo); 
    }

    if (loading || !user) {
      return (
        <p className="user-loading">Loading ...</p>
      );
    }
    return (
      <div id="user-details">
        <div className="nav">
          <Link className="nav-back-to-list" to="/">Back to the list</Link>
        </div>
        <div id="user-details-icon">
          <img src={`/images/user_${user.gender}.png`} alt={`generic user ${user.gender} icon`} />
        </div>
        <table>
          <tbody><tr><td>Nickname</td><td>{user.nickname}</td></tr></tbody>
          <tbody><tr><td>Name</td><td>{user.name}</td></tr></tbody>
          <tbody><tr><td>Gender</td><td>{user.gender}</td></tr></tbody>
          <tbody><tr><td>Age</td><td>{user.age}</td></tr></tbody>
        </table>
        <h1>Records</h1>
      </div>
    )
  }
}
Sign up to request clarification or add additional context in comments.

1 Comment

It acts exactly the same still. The issue isn't that the user value itself is null, ever, because axios sets it to the JSON from backend in componentDidMount and I don't have any problems with console.log(user). The only issue is when I try to log anything deeper like user.exercises[0] or user.exercises[0].exercise or user.exercise[0].exercise.name. Basically anything in exercises key.
0

Try to move your axios request to componentWillMount() { ... } lifecycle method.

3 Comments

No change unfortunately.
and set a user as the response from axios call then(res => { this.setState({ user: res.data, loading: false }); }
I don't know if I understand what You're suggesting correctly, but I believe You think that I don't get any proper data from axios request. However I do and I don't have any problems logging the whole response, but when trying to access values in exercises I keep getting errors. I've updated the question with data I get when logging the whole info and userInfo variables.
0

Probably you are not getting react component instance "this" after axios get call, try this:

componentDidMount() {
    this.setState({
      loading: true
    });

    let _this = this;
    axios.get(`http://192.168.0.248:3001/api/v1/users/${this.props.match.params.id}`)
      .then(res => res.data)
      .then(user => {
        _this.setState({
          user: user,
          loading: false
        });
      });
  }

2 Comments

I don't know if I understand what You're suggesting correctly, but I believe You think that I don't get any proper data from axios request. However I do and I don't have any problems logging the whole response, but when trying to access values in exercises I keep getting errors. I've updated the question with data I get when logging the whole info and userInfo variables.
Exactly, that's the issue. Every time I try I get the error exercises is null. Maybe it's a problem in data structure? However I basically copied pokeapi.co/api/v2/pokemon/1 example.
0

OK. So after a couple of different projects looping/mapping through different kinds of array I finally found a solution and wanted to share that with anyone who might have troubles with this silly problem.

Basically, Shubham Khatri was right suggesting to make a null check (whether a value exists). However, it was done on the wrong key. Since there are always some users in my database, we don't need to perform a null check on it. What I needed to do instead is to do a null-check of the "records" array inside "exercices" array. Not all users have submitted their results yet, therefore there's no data to perform operation on while looping/mapping. After adding a check if there are any records in each users the app works just fine showing results of users that have those and showing 'No results to show yet' for those that haven't posted any.

I've learnt recently that it's a good practice to learn, which keys are required in an API, so that we don't have to perform null checks on them and which keys are NOT required and might be missing in some results, where we should absolutely do null checks. Or if we build our own API we should specify ourselves, which ones will occur in every single object and which ones are optional.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.