react setState() is not working on first click but works fine on 2nd,3rd......clicks

Viewed 712

setState() doesn't work on first click ! the state value gets updated only on second , third ....clicks. i used proper contexts and imports to handle the states!

I'll quickly summarize what im doing top nav bar has two buttons , home and cart. Side nav bar has three hero buttons, on click renders the respective hero store which has tshirts , socks and shoes with + and - buttons to increase or decrease the quantity.

on each click the value of span that displays the quantity increases correctly but the cart buttons shows the quantity excluding the first clicks. Like when i increment the tshirts value to 1 , the cart button doesn't show any value ,as i increment the tshirts value to 2 the cart button shows 1

cartButton uses the state CartValue tshirts,socks,shoes use the state HeroGoods

(live demo) click here to see the what im talking about

i'm not sure if im allowed to post all the components and external links like github here. but anyways if you guys cant see where i went wrong from the code below , here's link to the github repo

import React , {useState,useEffect}from 'react';
import Navmenu from './Navmenu'
import SideNav from './SideNav'
import ActionDiv from './ActionDiv'
import ActionHeroStore from './ActionHeroStore'
import ActionCart from './ActionCart'
import '../css/main.css'

export const HeroContext=React.createContext()
const emptyGood={
  tshirts:0,
  shoes:0,
  socks:0,
}
const emptyCart={
  batman:{
    tshirts:0,
    shoes:0,
    socks:0,
  },
  superman:{
    tshirts:0,
    shoes:0,
    socks:0,
  },
  greenlantern:{
    tshirts:0,
    shoes:0,
    socks:0,
  },
}
function empty()
{
  return null
}
function App() {
  const [hero,setHero]=useState(null)
  const [cartValue,setCartValue]=useState(emptyCart)
  const [batmanGoods,setBatmanGoods]=useState(emptyGood)
  const [supermanGoods,setSupermanGoods]=useState(emptyGood)
  const [greenLanternGoods,setGreenLanternGoods]=useState(emptyGood)
  const [showCart,setShowCart]=useState(false)
  
  function handleUpdateGoods(hero,obj){
    hero=='batman'?
    setBatmanGoods(prevState=>{
      return {...prevState,...obj}
    }):
    hero=='superman'?
    setSupermanGoods(prevState=>{
      return {...prevState,...obj}
    }):
    hero=='greenlantern'?
    setGreenLanternGoods(prevState=>{
      return {...prevState,...obj}
    }):
    empty()
  }

  function handleHeroSelect(name){
    setHero(prevState=>prevState=name)
  }

  function handleCartValue(value)
  {
    setCartValue(value)
  }

  function handleShowCart(status)
  {
    setShowCart(status)
  }

  function giveHeroGoods(hero,element)
  {
    return (
    hero=='batman'?batmanGoods[element]:
    hero=='superman'?supermanGoods[element]:
    hero=='greenlantern'?greenLanternGoods[element]:empty()
    )
  }
  function handleUpdateCart(name){
    name=='batman'?
    setCartValue(prevState=>{
      return {...prevState,batman:{...batmanGoods}}
    }):
    name=='superman'?
    setCartValue(prevState=>{
      return {...prevState,superman:{...supermanGoods}}
    }):
    name=='greenlantern'?
    setCartValue(prevState=>{
      return {...prevState,greenlantern:{...greenLanternGoods}}
    }):
    empty()
  }
  
  const heroContextValue={
    handleHeroSelect,
    handleCartValue,
    handleUpdateGoods,
    giveHeroGoods,
    handleUpdateCart,
    handleShowCart
  }
  
  return (
    <>
    <HeroContext.Provider value={heroContextValue}>
   <Navmenu cartValue={cartValue}/>
   <div className="mainContent">
     <SideNav cartValue={cartValue}/>
    {hero==null && !showCart &&<ActionDiv/>}
    {hero!==null && !showCart && <ActionHeroStore hero={hero}/>}
    {showCart && <ActionCart cartValue={cartValue}/>}
   </div>
   </HeroContext.Provider>
   </>
  )
}

export default App;
import React ,{useContext} from 'react'
import {HeroContext} from './App'

export default function Navmenu(props) {
    const {cartValue}=props
    const {handleHeroSelect,handleShowCart}=useContext(HeroContext)

    function giveGoodsSum(obj)
    {
        return obj.tshirts+obj.socks+obj.shoes
    }
    function giveCartValue(cartValue){
        let sum=0
        for(let key in cartValue)
        {
           sum=sum+giveGoodsSum(cartValue[key])
        }
        return(
            sum!==0?sum:null
        )
    }
    return (
        <div
         className="navMenu"
        >
            <button 
            className="homeButton"
            onClick={()=>{
                handleHeroSelect(null)
                handleShowCart(false)
            }}
            >
                Home
            </button>
            <button
            className="cartButton"
            onClick={()=>{
                handleHeroSelect(null)
                handleShowCart(true)
            }}
            >
                cart 
                <span
                >
                 {giveCartValue(cartValue)}
                </span>
            </button>
        </div>
    )
}
import React ,{useContext} from 'react'
import {HeroContext} from './App'
export default function SideNav() {
    const {handleHeroSelect}=useContext(HeroContext)
    return (
        <div className="sideNav">
            <div 
            className="batman"
            onClick={()=>handleHeroSelect('batman')}
            />
            <div 
            className="superman"
            onClick={()=>handleHeroSelect('superman')}
            />
            <div 
            className="greenlantern"
            onClick={()=>handleHeroSelect('greenlantern')}
            />
        </div>
    )
}
import React  from 'react'
import ActionHeroStoreGoods from './ActionHeroStoreGoods' 
export default function ActionHeroStore(props) {
    const {hero}=props
    return (
        <div className={`actionHeroStore ${hero}div`}>
            <h3>{hero}</h3>
            <div className="actionHeroStore_goods">
                <ActionHeroStoreGoods hero={hero}/>
            </div>
        </div>
    )
}
import React, { Fragment,useContext } from 'react'
import {HeroContext} from './App'
export default function ActionHeroStoreGoods({hero}) {
    const {giveHeroGoods,
            handleUpdateGoods,
            handleUpdateCart
          }=useContext(HeroContext)
    const goods=['tshirts','shoes','socks'];
    const goodsElement=goods.map((element,index) => {
        return <Fragment  key={index}>
        <div className="soloGood">
            <span>{element}</span>
            <button 
            onClick={
                ()=>decrement(hero,element)
            }>-</button >
            <span>{giveHeroGoods(hero,element)}</span>
            <button onClick={
                ()=>{
                    increment(hero,element)
                    handleUpdateCart(hero)
                }
                }>+</button>
            
        </div>
        </Fragment>
    })
    
    function increment(hero,element){
            let updateObj={};
            updateObj[element]=giveHeroGoods(hero,element)+1
            handleUpdateGoods(hero,updateObj)
    }
    function decrement(hero,element){
        if(giveHeroGoods(hero,element)>0)
        {   
            let updateObj={};
            updateObj[element]=giveHeroGoods(hero,element)-1
            handleUpdateGoods(hero,updateObj)
        }
    }
    return (
        <>
        {goodsElement}
        </>        
    )
}
1 Answers

The problem is not in setState. The problem in the code. handleUpdateCart() function is called before the *Goods states are changed. So It works with old data. If you will add in the your 'App.js' file the following fragment:

...
...
  function giveHeroGoods(hero,element)
  {
    return (
    hero=='batman'?batmanGoods[element]:
    hero=='superman'?supermanGoods[element]:
    hero=='greenlantern'?greenLanternGoods[element]:empty()
    )
  }
// FROM HERE
  React.useEffect(() => {
    handleUpdateCart('batman');
  }, [
    batmanGoods
  ]);

  React.useEffect(() => {
    handleUpdateCart('superman');
  }, [
    supermanGoods
  ]);

  React.useEffect(() => {
    handleUpdateCart('greenlantern');
  }, [
    greenLanternGoods
  ]);

// TILL HERE
  function handleUpdateCart(name){
...
...
Related