简体   繁体   中英

render method being called before API data is loaded in React

Total beginner with React. I am trying to work out the standard approach to this situation in React.

I am accessing an api, the data is being returned all ok, except I am trying to set the data as a state of my component, and the render() method is referencing the state before any data is returned so the state property is being defined as 'null'.

In my code sample below you can see I am logging to the console, and despite the order of things, the second log is being returned from the browser before the one that has setState to be the API data.

Any help / explanation as to why this is happening despite using .then() would be appreciated.

Thank you.

PS: I have removed the TeamList component for simplification, but like the 'second log', the component gets rendered before the data has actually been pulled in.

import React, { Component } from 'react';
class App extends Component {

constructor(props) {
  super(props);
  this.state = {
    data: null,
  }
}

componentDidMount() {
  const uri = 'http://api.football-data.org/v2/competitions/PL/teams'; 

  let h = new Headers()
  h.append('Accept', 'application/json')
  h.append('X-Auth-Token', 'XXXXXXXXXXXXXXXXXXXX')
  let req = new Request(uri, {
    method: 'GET',
    headers: h,
    mode: 'cors' 
  })

  var component = this;

  fetch(req)
   .then( (response) => {
    return response.json()    
   })
   .then( (json) => {
      this.setState({ data: json })
   })
   .then( (json) => {
      console.log( 'second log', this.state.data )
   })
   .catch( (ex) => {
      console.log('parsing failed', ex)
   })
   console.log( 'first log', this.state.data )
}

render() {
  return (
    <div>
      <div className="App">
      <TeamList list={this.state.data} />
    </div>

    </div>
  );
}
}

export default App;

When you mount a component, it gets rendered immeadiately with the initial state (that you've set in the constructor). Then later, when you call setState , the state gets updated and the component gets rerendered. Therefore it makes sense to show something like "loading..." until state.data is not null:

 render() {
  return (
    <div>
      <div className="App">
      {this.state.data ? <TeamList list={this.state.data} /> : "loading..." }
    </div>
    </div>
  );
 }

Now additionally logging does not work as expected as setState does not return a promise, so:

    .then( (json) => {
      this.setState({ data: json })
   })
   .then( (json) => {
     console.log( 'second log', this.state.data )
   })

is actually the same as:

    .then( (json) => {
       this.setState({ data: json })
       console.log( 'second log', this.state.data )
   })

and that still logs null as setState is asynchronous, which means that calling it does not change this.state now but rather somewhen . To log it correctly use the callback:

 then( (json) => {
       this.setState({ data: json }, () => {
           console.log( 'second log', this.state.data )
       });
   })

You need to add something like this to the start of your render():

if (this.state.data === null) {
  return false;
}

So your code should be:

render() {
  if (this.state.data === null) {
    return false;
  }

  return (
  <div>
    <div className="App">
      <TeamList list={this.state.data} />
    </div>
  </div>
  );
}

render() is called immediately, but you want it to return false until this.state.data has data

Just an idea:

import React, { Component } from 'react';

class App extends Component {

    constructor(props) 
    {
      super(props);

      this.state = {
       data: null,
      };

    }

    componentDidMount() 
    {
        fetch('http://api.football-data.org/v2/competitions/PL/teams')
        .then(response => response.json())
        .then(data => this.setState({ data }));
    }

    render() {
        return (
           <div>
                <div className="App">
                    <TeamList list={this.state.data} />
                </div>

           </div>
        );
    }
}

export default App;

TeamList :

class TeamList extends React.Component {

    constructor(props) {
        super(props);
    }

    render(){

       return (
            <ul>
            {
                this.props.list.map((element, i) => {
                     return (
                        <li className="un-res t_d " key={i}>{element}</li>
                     )
                }
            })
     }
}

export default TeamList

Happy coding!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM