1

I'm trying to display game information from the Steam API in a React Native Flatlist. I'm new to React and JSX, so a lot of what I'm reading doesn't make sense.

I want the Flatlist to display a list of game titles owned by a particular account. The data returned from Steam's API call (via fetch) looks like this:

{
"response": {
"game_count": 69,
"games": [
  {
    "appid": 220,
    "name": "Half-Life 2",
    "playtime_forever": 24,
    "img_icon_url": "fcfb366051782b8ebf2aa297f3b746395858cb62",
    "img_logo_url": "e4ad9cf1b7dc8475c1118625daf9abd4bdcbcad0",
    "has_community_visible_stats": true,
    "playtime_windows_forever": 0,
    "playtime_mac_forever": 0,
    "playtime_linux_forever": 0
  },
  {
    "appid": 320,
    "name": "Half-Life 2: Deathmatch",
    "playtime_forever": 0,
    "img_icon_url": "795e85364189511f4990861b578084deef086cb1",
    "img_logo_url": "6dd9f66771300f2252d411e50739a1ceae9e5b30",
    "has_community_visible_stats": true,
    "playtime_windows_forever": 0,
    "playtime_mac_forever": 0,
    "playtime_linux_forever": 0
  },

and so on. Since I'm trying to display a list of games by name, the name attribute is the only one I need.

The data lists each game as an anonymous object, so I can't access the properties within each game using dot notation like I normally would. I tried using a for loop to iterate through them, but that doesn't work either. From my research, it seems like people normally use an Array.map for this kind of thing, but I'm unclear if that can be used with Objects.

Another problem I've encountered is the Flatlist keyExtractor property. I know it's supposed to be an anonymous function that returns some unique index or property about each Flatlist item, for the purpose of making the structure more efficient and to allow it to track updates to the list. However, I have no idea how to create this function myself. I think the appid field from the JSON data would be a good candidate, but I'm not sure how to get that into the keyExtractor function.

So, to put it as a question: How would I go about displaying data from a JSON object containing anonymous sub-objects in a Flatlist, and how would I populate the keyExtractor of that list with a different data entry (the appid from that list?

Below is my starting code:

import React, {Component} from 'react';
import {FlatList, Stylesheet, Text, View} from 'react-native';

export default class App extends Component {
  state = {
    dataset: []
  };

  componentWillMount() {
    this.fetchData();
  }

  fetchData = async () => {
    const response = await fetch("<API URL>");
    const json = await response.json();
    //const data = json.map((item) => item.games.name);
    var key = 0;
    const data = json[games][0][name];
    this.setState({ dataset: data });
  }

  render() {
      console.log(this.state.dataset);
    return (
      <View>
        <FlatList
          data={this.state.dataset}
          keyExtractor={(x, i) => i} //I have no idea what this does, or if it makes sense here.  
                                     //Where do x and i come from? (I got this from a tutorial video
                                     //and this was glossed over)

          renderItem={({ item }) => //Where does item come from?
            <Text>
              {item}
            </Text>
          }
        />
      </View>
    );
  }
}
Tyme96
  • 13
  • 3

1 Answers1

1

Alright, it seems you're having a few minor problems with understanding how FlatList works. Let me break it down for you.

Let's start with the Steam API request. In your example, you're first declaring dataset as an empty array in your state, then trying to update it with the result of a network request which is the way to go. The problem is, when you do json['games'][0]['name'] you're accessing the first item (index 0) of the games array and getting its name property and then setting that name as your dataset. Although you forgot the quotes around property names, it won't work. What you need to do instead is something like this:

fetchAllGames = async () => {
    const steamResponse = await fetch("<API URL>");
    const json = await steamResponse.json();

    // We get all the games back from Steam in the form of an array
    this.setState({ games : json.games });
}

We're now correctly updating the array inside our state with the data from the games array.

Let's move on to the keyExtractor and renderItem functions. The keyExtractor function is used to tell React about a unique identifier for each of your list items. In this case, this would be the appid property of a game. React then uses this unique ID to differentiate between items and determine which ones need updating. This function provides you with two parameters, namely the actual item and its index. Using these, we can then do something like this:

keyExtractor = (item, index) => {
    return item.appid.toString();
}

We're now returning the appid property as a string (which is the type React expects key to be).

The renderItem function is a bit different, React is providing you with a parameter which contains your item plus a lot of other properties. Since we're only interested in the actual item, we're destructuring it using brackets like so: { item }. This is a technique commonly used in JavaScript to "extract" properties from objects. It is normally used like this:

const testObj = {
    name : "John",
    surname : "Doe"
}

const { name, surname } = testObj; 

This way, you can directly refer to name and surname as if they were independent variables. Another way of doing this would be:

const testObj = {
    name : "John",
    surname : "Doe"
}

const name = testObj.name;
const surname = testObj.surname;

I hope this cleared some of the questions you might've been asking yourself! Here's the complete working code below. You may notice I moved some inline functions to class members, this is just a performance optimization to prevent the functions from being recreated on every render, you can ignore that.

import React, { Component } from 'react';
import { FlatList, Text } from 'react-native';

export default class App extends Component {

    state = {
        games : []
    };

    componentDidMount() {
        this.fetchAllGames();
    }

    fetchAllGames = async () => {
        const steamResponse = await fetch("<API URL>");
        const json = await steamResponse.json();

        // We get all the games back from Steam in the form of an array
        this.setState({ games : json.response.games });
    }

    keyExtractor = (item, index) => {
        return item.appid.toString();
    }

    renderItem = ({item}) => {
        return (
            <Text>{item.name}</Text>
        );
    }

    render() {
        return (
            <FlatList 
                data={this.state.games}
                keyExtractor={this.keyExtractor}
                renderItem={this.renderItem} />
        );
    }
}

EDIT #1 - As pointed out by the OP, I made a typo and corrected it. I also changed the JSON object to reflect the response property.

emeraldsanto
  • 4,330
  • 1
  • 12
  • 25
  • Thanks so much for your help, and the time you put into this reply. :) – Tyme96 Dec 17 '19 at 16:09
  • Follow up comment due to editing restrictions. There were some things I had to change to get the code to work. `renderItem=(this.renderItem)` became `={this.renderItem}`, (syntax error). I had to process the JSON object I got from Steam (`let data = JSON.parse(JSON.stringify(json))`). I also had to change the reference in `setState` to `data.response.games`. (For anyone who reads this in the future, I got my info for the JSON trick from [link](https://stackoverflow.com/questions/17546953/cant-access-object-property-even-though-it-shows-up-in-a-console-log)) – Tyme96 Dec 17 '19 at 16:20
  • Good job fixing the typos! I corrected both of your points in an edit. I don't think you need to use `JSON.parse()` though, that's why `steamResponse.json()` returns an object. – emeraldsanto Dec 17 '19 at 16:39