1

I'm trying to update state within an onClick event handler according to props. in the console.log I'm getting empty string as the initial state value.

I already tried to change the component to function using state hooks instead of state. I tried to use method to update the state when onClick is invoke. I already tried to use the suggestions in the answers of a similar questions, this case is different, maybe because I'm using the setState within the event handler.

does any one know what is the problem here ? thanks in advance!

class DropDownItem extends Component {
  constructor(props) {
    super(props)
    this.state = {
      selectedItem : "" 
    }
    this.updateItem = this.updateItem.bind(this)
  }

  updateItem (item) {
    this.setState({selectedItem : item})
  }

  render(){
    return (
      <div>
        <DropdownItem onClick={() => {
            this.updateItem(this.props.product)
            console.log("item",this.state.selectedItem)
          }}  
        >{this.props.product}</DropdownItem>
        <DropdownItem divider/>
      </div>
    )
  }
}

here is the body of the parent component :

render() {




  const productItems = this.state.products.map((productValue,index) => {
    return(<DropDownItem key={index} product={productValue.name}  />)
  })

    return (
<div>

  {this.state.products[0] && Array.isArray(this.state.products)  ? 
     <div>
       <DropdownComponent
      isOpen={this.state.dropdownOpen} 
      toggle={this.toggle}
      product={productItems}

       />
     </div> :
     <div>loading...</div>}     
</div>

    )}

I want to update state in order to show the clicked dropdown item to the user.

Amitco
  • 55
  • 1
  • 7
  • 1
    `this.setState` is async, meaning when you log `this.state.selectedItem` it's still empty. Clicking it again should log the changed state. –  May 03 '19 at 20:29
  • 1
    What do you get if you console.log `this.props.product`? Can you show us how your component is being used (i.e. what props are you passing to it)? – quicklikerabbit May 03 '19 at 20:30
  • 2
    setState is asynchronous. Try: this.setState({selectedItem: item}, ()=> { console.log("item", this.state.selectedItem) }); as described here https://medium.learnreact.com/setstate-takes-a-callback-1f71ad5d2296 – camen6ert May 03 '19 at 20:30
  • 1
    Possible duplicate of [React setState not updating state](https://stackoverflow.com/questions/41446560/react-setstate-not-updating-state) – csbarnes May 03 '19 at 20:33
  • thank you for the comments. In the console log I'm getting this.props.product as the desired value. I tried @camen6ert suggestion,Unfortunately I'm getting the same result. – Amitco May 04 '19 at 04:53
  • I'm confused, why is your DropDownItem class (it appears to be a single dropdown item in a list) storing the information of whether or not it's selected inside its own state? Shouldn't this be the container's job, passing an onClick bound to a particular list item down to the children? Where are you consuming this.state.selectedItem other than the log statement? – Hypaethral May 04 '19 at 05:02
  • yes you are right. this was my first option, but I couldn't make it work so I tried to manage the state in my child component (DropDownItem) for now I want to update the state and then I will show it to the user in my DropDownItem component. does it make sense ? or should I do it in alternative way? – Amitco May 04 '19 at 05:17

2 Answers2

1

setState() batches updates, so this.state will not immediately reflect the update.

The solution, as @camen6ert already pointed out, is to read this.state in setState()'s callback:

class DropDown extends React.Component {
  constructor(props) {
    super(props)
    this.state = {selectedItem: null}
  }
  updateItem(selectedItem, callback) {
    this.setState({selectedItem}, callback)
  }
  render() {
    return (
      <select onChange={event => {
        this.updateItem(event.target.value,
          () => console.log('item', this.state.selectedItem))
      }}>
        <option>A</option>
        <option>B</option>
      </select>
    )
  }
}

ReactDOM.render(<DropDown />, document.getElementById('root'))
<script src="//cdnjs.cloudflare.com/ajax/libs/react/16.8.6/umd/react.production.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.6/umd/react-dom.production.min.js"></script>
<div id="root"></div>
P Varga
  • 19,174
  • 12
  • 70
  • 108
1

You should get the updated state inside the callback provided as second argument in setState, like this

class DropDownItem extends Component {
  constructor(props) {
    super(props)
    this.state = {
      selectedItem : "" 
    }
    this.updateItem = this.updateItem.bind(this)
  }

  updateItem (item, callback) {
    this.setState({selectedItem : item}, callback)
  }

  render(){
    return (
      <div>
        <DropdownItem onClick={() => {
            this.updateItem(this.props.product, ()=>{
                console.log("item",this.state.selectedItem)
            })
          }}  
        >{this.props.product}</DropdownItem>
        <DropdownItem divider/>
      </div>
    )
  }
}
Shridhar Sharma
  • 2,337
  • 1
  • 9
  • 13