0

I am working on this FreeCodeCamp leaderboard table and and when clicking in the highlighted table header the application calls either this url https://fcctop100.herokuapp.com/api/fccusers/top/recent or this one https://fcctop100.herokuapp.com/api/fccusers/top/alltime thus sorting between campers with the highest points for the past 30 days or all time.

My issue here is that I have to click twice in order to get the desired results. In the CamperLeaderboard component handleSort function when I console.log the state does not change until I have clicked twice

Click Once

handleSort = (sort) => {
  console.log(sort);  // alltime
  console.log(this.state.sort)  //recent
  this.setState({ sort: sort });
  console.log(this.state.sort)  //recent
  this.getData();
};

Click Twice

handleSort = (sort) => {
  console.log(sort);  // alltime
  console.log(this.state.sort)  //alltime
  this.setState({ sort: sort });
  console.log(this.state.sort)  //alltime
  this.getData();
};

This is the CodePen preview and below is the full code

/**
  Table body component
*/
class Table extends React.Component {
  handleSort = (e, sort) => {
    this.props.handleSort(sort);
  };

  renderCampers = (key, count) => {
    const camper = this.props.campers[key];
    return(
      <tr key={key}>
        <td>{count}</td>
        <td>
          <a href={`https://www.freecodecamp.com/${camper.username}`} target='_blank'>
            <img src={camper.img} />
            {camper.username}
          </a>
        </td>
        <td className='center'>{camper.recent}</td>
        <td className='center'>{camper.alltime}</td>
      </tr>
    )
  };

  render() {
    let count = 0;
    return (
      <div>
        <table>
          <caption>Leaderboard</caption>
          <tr>
            <th>#</th>
            <th>Camper Name</th>
            <th onClick={(e) => this.handleSort(e, 'recent')}><a href='javascript:void(0)'>Points in the past 30 days</a></th>
            <th onClick={(e) => this.handleSort(e, 'alltime')}><a href='javascript:void(0)'>All time points</a></th>
          </tr>
          {Object.keys(this.props.campers).map(key => {
            count++;
            return this.renderCampers(key, count);
          })}
        </table>
      </div>
    );
  }
}

/**
  Container
*/
class CamperLeaderboard extends React.Component {
  state = {
    campers: [],
    sort: 'recent'
  };

  getData() {
    let url = `https://fcctop100.herokuapp.com/api/fccusers/top/${this.state.sort}`
    const self = this;
    axios.get(url)
      .then(function (response) {
        self.setState({ campers: response.data });
        //console.log(self.state.campers);
      })
      .catch(function (error) {
        console.log(error);
      });
  }

  componentWillMount() {
    this.getData();
  }

  handleSort = (sort) => {
    this.setState({ sort: sort });
    this.getData();
  };

  render() {
    return (
      <div>
        <p>Click links in table header to sort</p>
        <Table campers={this.state.campers} 
              handleSort={this.handleSort} />
      </div>
    );
  }
}

ReactDOM.render(<CamperLeaderboard />, document.getElementById('app'));

/*
To get the top 100 campers for the last 30 days: https://fcctop100.herokuapp.com/api/fccusers/top/recent.
To get the top 100 campers of all time: https://fcctop100.herokuapp.com/api/fccusers/top/alltime.
*/
esausilva
  • 1,964
  • 4
  • 26
  • 54
  • Does the UI show the sorting take place only after the second time? Your console.log in the first example that take place right after setting state will almost always not show updated state because `this.setState` is async meaning it will go into an event queue and eventually be updated. the consoleLog will still show the previous state because its happening before the actual state has been updated. But if your UI isn't changing once you setstate then something else is also happening. – finalfreq Jan 03 '17 at 18:16
  • yes, the UI shows the sorting only after the second time I click in either of the headings – esausilva Jan 03 '17 at 18:25

1 Answers1

2

I believe @finalfreq's explanation is correct and this is how to fix it.

Update handleSort method of the CamperLeaderboard class like this:

handleSort = (sort) => {
  this.setState({ sort: sort });
  // You don't need to read sort from state. Just pass it :)
  this.getData(sort);
}
  • 1
    Sounds correct, you cannot expect `setState` to work in the same turn, the docs say `setState() does not immediately mutate this.state but creates a pending state transition. Accessing this.state after calling this method can potentially return the existing value. There is no guarantee of synchronous operation of calls to setState and calls may be batched for performance gains.` http://stackoverflow.com/questions/30782948/why-calling-react-setstate-method-doesnt-mutate-the-state-immediately – Ruan Mendes Jan 03 '17 at 18:43
  • @R. Haluk Öngör this is a "duh" moment for me, your answer works great. – esausilva Jan 03 '17 at 19:21
  • 1
    @JuanMendes reading your linked question, I can also do this and works great `this.setState({ sort }, () => this.getData(this.state.sort));` – esausilva Jan 03 '17 at 19:23