33

In React I am trying to make a button increment a value stored in state. However using the code below function my value is set undefined or NaN when using handleClick.

class QuestionList extends React.Component {
  constructor(props) {
    super(props);
    this.state = {value: 0};

    // This binding is necessary to make `this` work in the callback
    this.handleClick = this.handleClick.bind(this);
  }

   handleClick = (prevState) => {
    this.setState({value: prevState.value + 1});
    console.log(this.state.value)
  }

Can you tell me why this is happening? it should be correct according to the docs here: https://facebook.github.io/react/docs/state-and-lifecycle.html

dwigt
  • 611
  • 1
  • 8
  • 18

8 Answers8

45

Because you are using the handleClick function incorrectly. Here:

handleClick = (prevState) => { .... }

prevState will be an event object passed to handleClick function, you need to use prevState with setState, like this:

handleClick = () => {
    this.setState(prevState => {
       return {count: prevState.count + 1}
    })
}

Another issue is, setState is async so console.log(this.state.value) will not print the updated state value, you need to use callback function with setState.

Check more details about async behaviour of setState and how to check updated value.

Check the working solution:

class App extends React.Component {
 
   constructor(props){
       super(props);
       this.state={ count: 1}
   }
 
  onclick(type){
      this.setState(prevState => {
         return {count: type == 'add' ? prevState.count + 1: prevState.count - 1}
      });
  }

   render() {
    return (
      <div>
        Count: {this.state.count}
        <br/>
        <div style={{marginTop: '100px'}}/>
        <input type='button' onClick={this.onclick.bind(this, 'add')} value='Inc'/>
        <input type='button' onClick={this.onclick.bind(this, 'sub')} value='Dec'/>
       </div>
     )
   }
}

ReactDOM.render(
  <App />,
  document.getElementById('container')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>

<div id='container'></div>
Mayank Shukla
  • 100,735
  • 18
  • 158
  • 142
3

set state is async so you wont see the value update when the console.log happens. You should have the state value printed out on the UI so you can see whats happening. To fix the console log try this.

class QuestionList extends React.Component {
  constructor(props) {
    super(props);
    this.state = {value: 0};
  }

   handleClick = (prevState) => {
    this.setState({value: prevState.value + 1}, () => {
        console.log(this.state.value)
    });
  }

NOTE: when you define an inline lambda (arrow function) for a react class this is bound correctly so you dont need to bind it in the constructor.

also you can change the way you pass the previous number if its just a state increment like this

handleClick = () => {
    this.setState({value: this.state.value + 1}, () => {
        console.log(this.state.value)
    });
}
John Ruddell
  • 25,283
  • 6
  • 57
  • 86
  • Thanks I needed to not use prevState. – dwigt Feb 21 '17 at 10:38
  • @dwigt it you notice i changed your console log to be in a callback for your setstate function. because an inline console log will execute when the thread hits it after the setstate function call not the completion of your state updating :) – John Ruddell Feb 21 '17 at 10:39
3

Hello there, try these codes to increment your value

class Counter extends React.Component{
 constructor(props){
   super(props);
     this.addOne = this.addOne.bind(this);
       this.state = {
         count : 0 
       }
    }

addOne() {                              // addOne as HandleClick
  this.setState((preState) => {
    return {
      count : preState.count + 1
      };
   });
 }

render() {
   return (
      <div>
        <h1>Count : {this.state.count}</h1>
        <button onClick={this.addOne}>+1</button>
      </div>
     );
   }
 }

ReactDOM.render(<Counter />, document.getElementById('YOUR-ID'));
2
class SkuVariantList extends React.Component {
    constructor(props) {
      super(props)
      this.state = {
        clicks: 0
      };
      this.clickHandler = this.clickHandler.bind(this)
    }

    componentDidMount() {
      this.refs.myComponentDiv.addEventListener('click', this.clickHandler);
    }

    componentWillUnmount() {
      //this.refs.myComponentDiv.removeEventListener('click', this.clickHandler);
    }

    clickHandler() {
      var clk = this.state.clicks
      this.setState({
        clicks: clk + 1
      });
    }

    render() {
      let children = this.props.children;

      return (
        <div className="my-component" ref="myComponentDiv">
          <h2>My Component ({this.state.clicks} clicks})</h2>
          <h3>{this.props.headerText}</h3>
          {children}
        </div>
      );
    }
  }
D V Yogesh
  • 3,337
  • 1
  • 28
  • 39
1

Try this out

class QuestionList extends React.component {

    constructor(props){
        super(props)
        this.state = {
            value : 0
        }
    }

    handleClick(){
        this.setState({
            value : this.state.value + 1
        })
    }

   render(){
        return( <button type="button" onClick={this.handleClick.bind(this)}> {this.state.value} </button> )
   }
}

Note that when you set a state, it triggers the render function, which will reflect the current state. Try it out in the browser!

ChannelJuanNews
  • 406
  • 3
  • 11
0
import React from 'react'

class App extends React.Component{
  constructor(){
    super()
    this.state = {
      count: 0
    }
    this.handleClick = this.handleClick.bind(this)
  }
  handleClick(){
    this.setState(prevState => {
      return {
        count: prevState.count + 1
      }
    })
  }
  render(){
    return(
      <div style = {{display: 'flex', fontSize: 30, flexDirection: 'column', alignItems:'center'}}>
        <h1>{this.state.count}</h1>
        <button onClick = {this.handleClick}>Change</button>
      </div>
    )
  }
}
export default App
Collins
  • 25
  • 5
0

This is the shortest code for that. First, initialize the state, then perform a method to increment.

state = {
    counter: 0
  }
increaseHandler = () => {
    let counter = this.state.counter
    counter += 1
    this.setState({counter: counter})
  }
howie
  • 2,587
  • 3
  • 27
  • 43
  • I just encountered this, because i used this and it works. However reactJS does not like you updating states without using setState. it's definitely the easiest solution but be careful doing this because they might make this an error at some point. – Pylot Jun 27 '20 at 23:27
0

You can do it this way also where we do both increment and decrement operation with same function making it more modular and redable

class CounterApp extends React.Component{

    constructor(){
        super();
        //here count is initially assigned with 0
        this.state ={
        count:0
        }
    }
    
    //when we click Increment or Decrement +1 or -1 is passed to step and the value gets changed it gets updated to the view
    increment = (step) =>{
        this.setState({
            count:this.state.count + step
        })
    }
    render(){
        const { count } = this.state;//same as const count = this.state.count;

        return(
            <div>
                <div className="counter-app">
                    <h2 className="value">{count}</h2>
                    <button onClick={() => this.increment(+1)}>Increment</button>
                    <button onClick={() => this.increment(-1)}>Decrement</button>
                </div>
            </div>
        )
    }
}
Sandeep Mukherjee
  • 673
  • 1
  • 9
  • 17