Array.prototype.map() method returns an empty array

I am working on freeCodeCamp’s Use the Twitchtv JSON API, using React, and JSONP for handling the UI, and API callback respectively.

In the render() method of my App's component, I want to create three <tr></tr> (table rows), that will each contain the names, and activities of streamers fetched from the Twitchtv API.

After calling the map() method on the stream array, which contains objects of streamers, the map() method returns an empty array, instead of an array of components.

Below is a copy of the App.js file, and have also created a gist on Github.

Thanks, for helping out.

import React, { Component } from "react";
import jsonp from "jsonp";

// TThe list of streamers given.
const streamers = [ "freecodecamp", "ESL_SC2", "OgamingSC2", "cretetion", "storbeck", "habathcx", "RobotCaleb", "noobs2ninjas" ];

// Where the details of streamers will be saved.
const stream = [];

// The JSONP callback.
const callJSONP = streamer => {
  // The Twitch.tv JSON API workaround provided by freeCodeCamp.
  const api = `https://wind-bow.glitch.me/twitch-api/streams/${streamer}`;
  let status = "Not Exist";
  
  jsonp(api, (err, data) => {
    try {
      if (err) {
        throw new Error("Something happened.");
      }

      if ( data.hasOwnProperty("stream") ) {
        status = data["stream"] ? `${data["stream"]["game"]}: ${data["stream"]["channel"]["status"]}` : "Offline";
      }

      stream.push({ streamer, status });
    } catch(err) {
      console.log(err.message);
    }
  });
};

// The body of the table.
const TableBody = props => (
  <tr key={props.streamer}>
    <td> { props.streamer } </td>
    <td> { props.status } </td>
  </tr>
);

// The head of the table.
const TableHead = () => (
  <thead>
    <tr>
      <th>Streamer</th>
      <th>Status</th>
    </tr>
  </thead>
);

export default class App extends Component {
  constructor(props) {
    super(props);

    // Populate the stream array.
    streamers.forEach(callJSONP);

    // Initialise the state object.
    this.state = {
      stream: stream
    };
  }
  render() {
    const stream = this.state.stream;

    // The stream array is an array of objects,
    // containing the names, and statuses of streamers.
    console.log(stream);

    const tbody = stream.map(TableBody);

    // This is where the LOGICAL BUG is.
    // The map method returns an empty array.
    console.log(tbody);

    return (
     <table>
      <TableHead />
      <tbody>
        { tbody }
      </tbody>
     </table> 
    );
  }
}

I posted the same question, with the same username, and topic.

Yes, I did, had to ask again, with more details added to the question.

Oh! Okay, would rather edit, than delete next time. Thank you.

Hi. I think the problem is that your callJSONP is asynchronous and is coming back after the page renders. You can see this by adding a log to that function (you’ll see they print out last, after the render method has run)

You can see that your ‘stream’ array is actually empty, if you add

console.log(stream.length);

after where you have

console.log(stream);

What makes this confusing is that the array seems to have values but as far as I can tell that’s the console being ‘helpful’ in a way I haven’t seen before. There’s a blue info symbol next to the output (in chrome) that says “the value below was evaluated just now” which I’m guessing is related??

I think the best way to fix it would be to make your JSONP callback a method on the component and call setState directly when each piece of data comes back.

Thanks for the insight, and the helpful response, would try your suggestion ASAP.

After I turned the callJSONP callback into a method in the App component, and called the this.setState method on the this.state.stream array, what I see logged in the console, is an handled exceptionn: Cannot read property 'state' of undefined.

Here’s a snippet of the code:

export default class App extends Component {
  constructor(props) {
    super(props);

    this.state = {
      stream: []
    };

    streamers.forEach(this.callJSONP);
  }
  callJSONP(streamer) {
    const api = `https://wind-bow.glitch.me/twitch-api/streams/${streamer}`;
    let status = "Not Exist";
    
    jsonp(api, (err, data) => {
      try {
        if (err) {
          throw new Error("Something happened.");
        }

        if ( data.hasOwnProperty("stream") ) {
          status = data["stream"] ? `${data["stream"]["game"]}: ${data["stream"]["channel"]["status"]}` : "Offline";
        }

        const stream = this.state.stream;
        stream.push( { streamer, status } );
        this.setState( { stream } );
      } catch(err) {
        console.log(err.message);
      }
    });
  }
  render() {
    const stream = this.state.stream;
    console.log(stream);

    const tbody = stream.map(TableBody);
    console.log(tbody);

    return (
     <table>
      <TableHead />
      <tbody>
        { tbody }
      </tbody>
     </table> 
    );
  }
}

Thanks, for reaching out a helping hand.

Hi. Sorry I can’t be more help at the moment, but I think the issue is that you need to bind this - it’s a bit of a pain but you will definitely need to learn how to do it, even if it doesn’t fully fix your issue right now. linky: https://medium.freecodecamp.org/react-binding-patterns-5-approaches-for-handling-this-92c651b5af56

edit to add: actually that article is a bit old, definitely don’t try to use React.createClass directly. Binding in the constructor is probably easiest.

Yeah, just did that, and that finally got it working. Thanks a whole bunch.

For the records, I think I am obligated to post the code snippet that finally got working,

Thanks a whole lot, to @camperextraordinaire, and @r1chard5mith, for reaching out their helping hands.

All I had to do was:

  • Change the callJSONP callback function into an instance method in the App's component.
  • Bind this, to this.callJSONP() method
  • Populate the this.state.stream array in the callJSONP() method, using this.setState() method.

Thanks once again to @r1chard5mith, for his helpful insights.

import React, { Component } from "react";
import jsonp from "jsonp";

// The list of streamers given.
const streamers = [ "freecodecamp", "ESL_SC2", "OgamingSC2", "cretetion", "storbeck", "habathcx", "RobotCaleb", "noobs2ninjas" ];

// The body of the table.
const TableBody = props => (
  <tr key={props.streamer}>
    <td> { props.streamer } </td>
    <td> { props.status } </td>
  </tr>
);

// The head of the table.
const TableHead = () => (
  <thead>
    <tr>
      <th>Streamer</th>
      <th>Status</th>
    </tr>
  </thead>
);

export default class App extends Component {
  constructor(props) {
    super(props);

    // Initialise the state object.
    this.state = {
      stream: []
    };

    // Bind the callJSONP instance method to this.
    this.callJSONP = this.callJSONP.bind(this);

    // Populate the this.state.stream array
    // with the details of streamers fetched
    // from the Twitch.tv API.
    streamers.forEach(this.callJSONP);
  }
  callJSONP(streamer) {
    // The Twitch.tv JSON API workaround provided by freeCodeCamp.
    const api = `https://wind-bow.glitch.me/twitch-api/streams/${streamer}`;
    let status = "Not Exist";
    
    jsonp(api, (err, data) => {
      try {
        if (err) {
          throw new Error("Something happened.");
        }

        if ( data.hasOwnProperty("stream") ) {
          status = data["stream"] ? `${data["stream"]["game"]}: ${data["stream"]["channel"]["status"]}` : "Offline";
        }

        const stream = this.state.stream;
        stream.push( { streamer, status } );
        this.setState( { stream } );

      } catch(err) {
        console.log(err.message);
      }
    });
  }
  render() {
    const stream = this.state.stream;
    const tbody = stream.map(TableBody);

    return (
     <table>
      <TableHead />
      <tbody>
        { tbody }
      </tbody>
     </table> 
    );
  }
}