Mac
Mac

Reputation: 327

Wait for object array to load and map() it. React

I am trying to loop through json_obj after it loads using async XMLHttpRequest() but as json_obj is null at the start map() crashes.

Here is my code:

import React, { Component, PropTypes} from 'react';
import ReactDOM from 'react-dom';


class ImageViewer extends React.Component {
    constructor() {
        super();
        this.state = {
            loading: true,
            json_obj : null,
            link: "https://api.github.com/users/github/repos"
        }
    }
    componentDidMount() {
        var xhr = new XMLHttpRequest();
        xhr.open("GET", this.state.link, true);
        xhr.onload = function (e) {
            if (xhr.readyState === 4) {
                if (xhr.status === 200) {
                    this.setState({ json_obj :JSON.parse(xhr.responseText).sort(function(a, b){var keyA = new Date(a.updated_at),keyB = new Date(b.updated_at);if(keyA > keyB) return -1;if(keyA < keyB) return 1;return 0;})});
                } else {
                console.error(xhr.statusText);
                }
            }
        }.bind(this);
        xhr.onerror = function (e) {
        console.error(xhr.statusText);
        };
        xhr.send(null);
    }
    render() {
    return (
            <div>
                {this.state.json_obj.map((obj, index) => {
                            return (
                                <div >
                                    <h1>{obj.name}</h1>
                                    <h1>{obj.updated_at}</h1>
                                </div>
                            )
                })}
            </div>
    )
}}
ReactDOM.render(
  <ImageViewer />,
  document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
: <div id="root"></div>

I tried to use conditional rendering but it throws errors as well:

import React, { Component, PropTypes} from 'react';
import ReactDOM from 'react-dom';


class ImageViewer extends React.Component {
    constructor() {
        super();
        this.state = {
            loading: true,
            json_obj : null,
            link: "https://api.github.com/users/github/repos",
            stuff : {name:"loading", updated_at:"loading"}
        }
    }
    componentDidMount() {
        var xhr = new XMLHttpRequest();
        xhr.open("GET", this.state.link, true);
        xhr.onload = function (e) {
            if (xhr.readyState === 4) {
                if (xhr.status === 200) {
                    this.setState({ json_obj :JSON.parse(xhr.responseText).sort(function(a, b){var keyA = new Date(a.updated_at),keyB = new Date(b.updated_at);if(keyA > keyB) return -1;if(keyA < keyB) return 1;return 0;})});
                } else {
                console.error(xhr.statusText);
                }
            }
        }.bind(this);
        xhr.onerror = function (e) {
        console.error(xhr.statusText);
        };
        xhr.send(null);
    }
    render() {
    return (
            <div>
                {(this.state.json_obj ? this.state.json_obj : this.state.stuff).map((obj, index) => {
                            return (
                                <div >
                                    <h1>{obj.name}</h1>
                                    <h1>{obj.updated_at}</h1>
                                </div>
                            )
                })}
            </div>
    )
}}
ReactDOM.render(
  <ImageViewer />,
  document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>

Upvotes: 0

Views: 3053

Answers (2)

Amin Jafari
Amin Jafari

Reputation: 7207

The first code you provided throws an error because json_obj is null as you mentioned. But the second one throws an error because you are trying to map over the object this.state.stuff which you can not do. So there are actually a couple of things you can do:

wrap stuff inside an array:

return (
            <div>
                {(this.state.json_obj ? this.state.json_obj : [this.state.stuff]).map((obj, index) => {
                            return (
                                <div >
                                    <h1>{obj.name}</h1>
                                    <h1>{obj.updated_at}</h1>
                                </div>
                            )
                })}
            </div>

or you can wrap it when defining it:

constructor() {
    super();
    this.state = {
        loading: true,
        json_obj : null,
        link: "https://api.github.com/users/github/repos",
        stuff : [{name:"loading", updated_at:"loading"}]
    }
}

or you can simply check if json_obj is available and rendering something else (or nothing) instead.

renders nothing if json_obj is not available

return (
            <div>
                {this.state.json_obj && this.state.json_obj.map((obj, index) => {
                            return (
                                <div >
                                    <h1>{obj.name}</h1>
                                    <h1>{obj.updated_at}</h1>
                                </div>
                            )
                })}
            </div>
    )

renders a loading message when json_obj is not available

return (
  <div>
    {
    this.state.json_obj ?
      this.state.json_obj.map((obj, index) => {
       return (
         <div >
           <h1>{obj.name}</h1>
           <h1>{obj.updated_at}</h1>
         </div>
       )
     }) : <div>Loading...</div>
    }
   </div>
  )

Upvotes: 1

nanobar
nanobar

Reputation: 66355

So check if it's null and return something else:

if (!this.state.json_obj) {
  return <div>Loading...</div>;
}

return (
  <div>
    {this.state.json_obj.map((obj, index) => (
      <div key={obj.name}>
        <h1>{obj.name}</h1>
        <h1>{obj.updated_at}</h1>
      </div>
    ))}
  </div>
)

Your code was throwing because this.state.stuff is an object and you can't iterate over an object with .map. If this.state.json_obj is also an object and not an array you would need to do Object.keys(this.state.json_obj).map(....

Upvotes: 3

Related Questions