Mileta Dulovic
Mileta Dulovic

Reputation: 1064

Why changing value in state doesn't trigger onChange of input element in REACT

I have input whose value is changed by button element. I need a way to call onChange of that input. I have some validation that has to be done when the value is changed.

I found some answers on StackOverflow but they are using dispatch which they didn't explain properly or I didn't use it properly.

But onChange is not called when the state is updated. Here is what I have by now

<div className="input-group">
  <input type="text" className="form-control quantity-field text-center" onChange={(e) => this.handleChange(e)} value={this.state.quantity}/>
  <button onClick={(e) => this.handleIncDec(e)}>+</button>
</div>
  handleIncDec = (e) => {
    e.preventDefault()

    this.setState({
      quantity: this.state.quantity += 1
    })
  }

handleChange = e => {
    console.log(e.target.value);

    const re = /^[0-9\b]+$/;
    if (e.target.value === '' || re.test(e.target.value)) {
      this.setState({
        quantity: e.target.value
      })
    }
  };

The value of the input is updated correctly as it should be but onChange is never called unless I updated value directly in that input and not with button, which is not what I want.

If you need any other info to tell me and I will provide it.

Upvotes: 1

Views: 3947

Answers (1)

christianeide
christianeide

Reputation: 427

The onChange-event will by default only be called on userinputs. Most of the time you can avoid trigger the onChange event programatically by using lifecycles-methods like ComponentDidUpdate or similar in React. In your example it looks to me like you only need to validte input from two different sources, so I would suggest an easier implementation.

Can you create a validationfunction instead that you can use on both handleChange and handleInDec?

You should also avoid updating state based on previous state with this.setState({ quantity: this.state.quantity += 1}), as you are not guaranteed that state always will be updated (Remember, setState is asynchronous). Instead use a the return value from setState that guarantees updated state-values

class Test extends React.Component {
  constructor() {
    super();

    this.state = {
      quantity: 0
    };
  }

  handleIncDec = e => {
    e.preventDefault();

    this.setState(prevState => {
      var newQuantity = prevState.quantity + 1;
      if (this.isValidInputData(newQuantity)) {
        return {
          quantity: newQuantity
        };
      }
    });
  };

  handleChange = e => {
    if (this.isValidInputData(e.target.value)) {
      this.setState({
        quantity: e.target.value
      });
    }
  };

  isValidInputData = newQuantity => {
    const re = /^[0-9\b]+$/;
    return newQuantity === "" || re.test(newQuantity);
  };

  render() {
    return (
      <div className="input-group">
        <input
          type="text"
          className="form-control quantity-field text-center"
          onChange={this.handleChange}
          value={this.state.quantity}
        />
        <button onClick={this.handleIncDec}>+</button>
      </div>
    );
  }
}

ReactDOM.render(<Test />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>

Upvotes: 2

Related Questions