HB11
HB11

Reputation: 25

Load data before rendering child elements React

I am trying to generate a list of selector options based on external JSON data. The code here is mostly good, except that part of it is being called before the data is loading resulting in no children being appended. I am sure there is a way to implement this but I'm not sure what that way is for my particular situation.

Here is the code:

class PokedexSelector extends Component {
  constructor(props) {
    super(props);

    this.state = {value: "National", pokedexes: []};

    this.generatePokedexList();

    this.handleChange = this.handleChange.bind(this);
    this.generatePokedexList = this.generatePokedexList.bind(this);
    this.pokedexList = this.pokedexList.bind(this);
  }

  handleChange(event) {
    this.setState({value: event.target.value});
  }

  generatePokedexList() {
    const pokedexes = [];
    fetch("https://pokeapi.co/api/v2/pokedex/")
    .then(response => response.json())
    .then(myJson => {
      let results = myJson["results"];
      results.forEach(function(pokedex) {
        let pokedexName = pokedex["name"];
        let pokedexLink = "https://pokeapi.co/api/v2/pokedex/" + pokedexName;
        let pokedexDisplayName = capitalize(pokedexName.replace('-',' '));
        pokedexes.push(
          {
            name: pokedexName,
            displayName: pokedexDisplayName,
            link: pokedexLink
          }
        );
      });
      this.state.pokedexes = pokedexes;
      console.log(this.state.pokedexes)
    })
  }

  pokedexList() {
    if (this.state.pokedexes.length > 0) {
      console.log("listing")
      return (
        this.state.pokedexes.map(pokedex => (
          <option>{pokedex.displayName}</option>
        ))
      )
    }
  }

  render() {
    return (
      <select id="pokedex-selector" value={this.state.value} onChange={this.handleChange}>
        {this.pokedexList()}
      </select>
    )
  }

}

export default PokedexSelector;

I tried using componentDidMount() as below, but I'm not sure how to specifically target one component for changes in this case (the <select> element).

componentDidMount() {
    {this.pokedexList()}
  }

Any ideas? Thanks!

Upvotes: 0

Views: 108

Answers (2)

Dinesh Pandiyan
Dinesh Pandiyan

Reputation: 6289

You should make your fetch calls before the render method is triggered, ideally in componentDidMount and store the response in the state. The component will re-render only when the state or props changes.

state should be updated via this.setState() method and should not be directly mutated using this.state.

In your case, since you're trying to mutate the state directly using this.state the component will not re-render. You should replace it with this.setState().

Try this code

class PokedexSelector extends Component {
  constructor(props) {
    super(props);

    this.state = {value: "National", pokedexes: []};

    this.handleChange = this.handleChange.bind(this);
    this.generatePokedexList = this.generatePokedexList.bind(this);
    this.pokedexList = this.pokedexList.bind(this);
  }

  componentDidMount() {
    this.generatePokedexList();
  }

  handleChange(event) {
    this.setState({value: event.target.value});
  }

  generatePokedexList() {
    const pokedexes = [];
    fetch("https://pokeapi.co/api/v2/pokedex/")
    .then(response => response.json())
    .then(myJson => {
      let results = myJson["results"];
      results.forEach(function(pokedex) {
        let pokedexName = pokedex["name"];
        let pokedexLink = "https://pokeapi.co/api/v2/pokedex/" + pokedexName;
        let pokedexDisplayName = capitalize(pokedexName.replace('-',' '));
        pokedexes.push(
          {
            name: pokedexName,
            displayName: pokedexDisplayName,
            link: pokedexLink
          }
        );
      });
      this.setState({pokedexes: pokedexes}); // use setState()
    })
  }

  pokedexList() {
    if (this.state.pokedexes.length > 0) {
      console.log("listing")
      return (
        this.state.pokedexes.map(pokedex => (
          <option>{pokedex.displayName}</option>
        ))
      )
    }
  }

  render() {
    return (
      <select id="pokedex-selector" value={this.state.value} onChange={this.handleChange}>
        {this.pokedexList()}
      </select>
    )
  }

}

export default PokedexSelector;

Upvotes: 2

RedPandaz
RedPandaz

Reputation: 6256

It should be this.setState and not this.state.pokedexes = pokedexes. Do not mutate state directly.

   this.setState({
          pokedexes
        })

Upvotes: 0

Related Questions