React: Help with callbacks

Hi everyone,

I am having some trouble with two functions executing in order in my React app. After some reading I suspect it has to do with JS being asynchronous. I have tried some things with callbacks that I read about but I can’t seem to get it working.

The two functions are called one after the other when I click a button called “Start”. The idea is that the game will figure out the stats of a character (like their maximum hp), and then give them full hp (their “temporary” hp).

When I do this the numbers come out wrong. However, if I only call calculateStats() in the start button it will set the correct maximum HP. Then, I press a “Test” button with fullHealth() and it correctly sets state to the right temporary hp (equal to the recently calculated max hp).

When I call these both sequentially in the Start button the stats come out like parts of each function were executed, which is why I suspect something with synchronicity but I can’t quite seem to understand how to make it work with callbacks, if that is even the solution. Any help would be appreciated!

        this.setState((prevState)=>{return{newGame:false}});
        console.log("startbutton "+JSON.stringify(this.state.playerStats))
        this.calculateStats()
        this.fullHealth()
        break

Here is my function for figuring out the max hp and setting it in state.

    let playerClass = this.state.playerClass;
    let lvl = this.state.playerStats.lvl;
    let hp = this.state.playerStats.hp;
    let mp = this.state.playerStats.mp;
    let equipmentStats = this.state.equipmentStats;
    let mainHand = this.state.mainHand;
    let offHand = this.state.offHand;
    let head = this.state.head;
    let body = this.state.body;
    let ring = this.state.ring;
    let str = mainHand.str+offHand.str+head.str+body.str+ring.str;
    let dex = mainHand.dex+offHand.dex+head.dex+body.dex+ring.dex;
    let int = mainHand.int+offHand.int+head.int+body.int+ring.int;
    let wDmg = mainHand.dmg + offHand.dmg
    let sDmg = function(){
      switch(true){
      case playerClass==="Warrior": return str; break
      case playerClass==="Rogue": return dex; break
      case playerClass==="Mage": return int; break
      default: //nothing
    }
    }
    let def = mainHand.def + offHand.def + head.def + body.def;
    let baseDmg = this.state.playerStats.lvl + 1
    let tDmg = wDmg + baseDmg + sDmg()
    let maxHp = str +(lvl * 2 + 5);
    let maxMp = int +(lvl * 2 + 2);
    equipmentStats = {dmg:wDmg,def:def,str:str,dex:dex,int:int}
    let playerStats = {lvl: lvl, hp: hp, maxHp: maxHp, mp: mp, maxMp: maxMp, baseDmg: baseDmg, tDmg: tDmg}
    this.setState((prevState)=>{return{equipmentStats}})
    this.setState((prevState)=>{return{playerStats}})
    console.log("stats set to "+JSON.stringify(playerStats));
  }

Here is the function to set the players temporary hp to their max hp.

  fullHealth = function(){
    let playerStats = this.state.playerStats
    let maxHp = this.state.playerStats.maxHp //this is getting 0 somehow
    console.log(maxHp)
    playerStats.hp=maxHp
    this.setState((prevState)=>{return{playerStats}})
  }

Hi @SnowdenWintermute,

The problem can be this line (without the repo I can test it):

Reacts Documentation:

https://reactjs.org/docs/state-and-lifecycle.html

this.setState((prevState, props) => ({
  counter: prevState.counter + props.increment
}));

Cheers and happy coding :slight_smile:

1 Like

@Diego_Perez thank you for taking a look at my issue. On your recommendation I have read that page in the documentation. I tried a few things based on that info, but have not had success. I’ve put the code up on GitHub if you wouldn’t mind looking at it.

EDIT:

I have circumvented the problem by adding a property to calculateStats that, if true, sets the player’s hp to their max hp. I am still curious as to why it wasn’t working the way I had it before, but since it works it is now a curiosity rather than an “urgent” problem.

Hi @SnowdenWintermute,

I used your first commit in the repo for the debuging process:

git checkout 08ae72f82

What I have found:

  • Because I was thinking the problem could be setState,
    I used grep to know where to look (where to put breakpoints in the debuger) :
$ grep -nr setState .


./components/NewGame.js:21:  this.setState({warScreen:true})
./components/NewGame.js:22:  this.setState({rogueScreen:false})
./components/NewGame.js:23:  this.setState({mageScreen:false})
./components/NewGame.js:30:  this.setState({warScreen:false})
./components/NewGame.js:31:  this.setState({rogueScreen:true})
./components/NewGame.js:32:  this.setState({mageScreen:false})
./components/NewGame.js:37:  this.setState({warScreen:false})
./components/NewGame.js:38:  this.setState({rogueScreen:false})
./components/NewGame.js:39:  this.setState({mageScreen:true})
./App.js:87:    this.setState((prevState)=>{return{equipmentStats}})
./App.js:88:    this.setState((prevState)=>{return{playerStats}})
./App.js:99:    this.setState((prevState)=>{return{playerStats}})
./App.js:125:   this.setState((prevState)=>{return{mainHand: mainHand}})
./App.js:126:   this.setState((prevState)=>{return{head}})
./App.js:127:   this.setState((prevState)=>{return{ring: ring}})
./App.js:134:   this.setState((prevState)=>{return{combatLog: tempLog+prevState.combatLog}})
./App.js:139:   this.setState((prevState)=>{return{combatLog: tempLog+prevState.combatLog}})
./App.js:152:   this.setState({playerStats})
./App.js:153:   this.setState((prevState)=>{return{combatLog: tempLog+prevState.combatLog}})
./App.js:165:   this.setState({enemyStats});
./App.js:166:   this.setState((prevState)=>{return{combatLog: tempLog+prevState.combatLog}})
./App.js:173:   this.setState((prevState)=>{return{playerClass:"Warrior"}})
./App.js:174:   this.setState({combatLog:"A Warrior enters the dungeon."})
./App.js:177:   this.setState((prevState)=>{return{playerClass:"Rogue"}})
./App.js:178:   this.setState({combatLog:"A Rogue enters the dungeon."})
./App.js:181:   this.setState((prevState)=>{return{playerClass:"Mage"}})
./App.js:182:   this.setState({combatLog:"A Mage enters the dungeon."})
./App.js:192:   this.setState({menuPage:1})
./App.js:195:   this.setState({menuPage:0})
./App.js:203:   this.setState({combatLog: ""})
./App.js:206:   this.setState((prevState)=>{return{newGame:false}});
  • Then I looked for playerStats
./components/PlayerStats.js:6:      <div className="menuBox" id="playerStats">
./App.js:24:  playerStats: {lvl: 1, hp: 0, maxHp: 0, mp: 0, maxMp: 0, baseDmg: 0,tDmg: 0},
./App.js:58:  let lvl = this.state.playerStats.lvl;
./App.js:59:  let hp = this.state.playerStats.hp;
./App.js:60:  let mp = this.state.playerStats.mp;
./App.js:80:  let baseDmg = this.state.playerStats.lvl + 1
./App.js:86:  let playerStats = {lvl: lvl, hp: hp, maxHp: maxHp, mp: mp, maxMp: maxMp, baseDmg: baseDmg, tDmg: tDmg}
./App.js:88:  this.setState((prevState)=>{return{playerStats}})
./App.js:89:  console.log("stats set to "+JSON.stringify(playerStats));
./App.js:93:  let playerStats = this.state.playerStats
./App.js:94:  let maxHp = this.state.playerStats.maxHp //this is getting 0 somehow
./App.js:96:  playerStats.hp=maxHp
./App.js:97:  //playerStats.maxHp=maxHp
./App.js:98:  //console.log(playerStats)
./App.js:99:  this.setState((prevState)=>{return{playerStats}})
./App.js:131: if(this.state.playerStats.hp<=0){
./App.js:144: const a = this.state.playerStats.hp;
./App.js:147: const playerStats = this.state.playerStats;
./App.js:148: playerStats.hp=d;
./App.js:152: this.setState({playerStats})
./App.js:158: const b = this.state.playerStats.tDmg;
./App.js:163: let tempLog = "Player hits Enemy for "+this.state.playerStats.tDmg+"\n";
./App.js:207: console.log("startbutton "+JSON.stringify(this.state.playerStats))
./App.js:212: console.log(this.state.playerStats)
./App.js:237: <CharPic hp={this.state.playerStats.hp} handleClick={this.handleClick}/>
./App.js:252: <PlayerStats stats={this.state.playerStats} eq={this.state.equipmentStats} playerClass={this.state.playerClass}/>

Debug time

I used the Chrome DevTools (67.0.3396.62 Official Build).

  • The problem was line 93 (App.js):
let playerStats = this.state.playerStats

You are getting a non-updated value (state is not updated yet).

  • Is the same reason why maxHP is 0 (line 94):
let maxHp = this.state.playerStats.maxHp //this is getting 0 somehow
  • Without a new value,this.state doesn’t have anything to update (line 99):
 this.setState((prevState)=>{return{playerStats}})

comments

You can make it work (but I think is not a good idea) changing the following:

  • Make the function call of fullHealth directly inside calculateStats

  • Pass an argument (the object playerStat) to fullHealth

  • In fullHealth overwrite the value of hp using maxHp

  • Then update the state

Example:

// inside calculateStats

calculateStats = function() {
...
 let playerStats = {lvl: lvl, hp: hp, maxHp: maxHp, mp: mp, maxMp: maxMp, baseDmg: baseDmg, tDmg: tDmg}
...

 this.setState({playerStats: playerStats});
 this.fullHealth(playerStats);
}

// inside fullHealth

fullHealth = function(playerStats) {
...
 let playerHP = {hp: playerStats.maxHp};

// overwrite the values, trying  to avoid data loss
 let playerStatsFullHP = Object.assign(playerStats, playerHP);

 this.setState({playerStats: playerStatsFullHP});
}
  • I wouldn’t recommend it because “is not the react way”, also, you are practicaly nesting
    updates ( harder to debug)

Cheers and happy coding :slight_smile:

1 Like