94

Edit: I don't want to call handleChange only if the button has been clicked. It has nothing to do with handleClick. I gave an example in the @shubhakhatri answer's comment.

I want to change the input value according to state, the value is changing but it doesn't trigger handleChange() method. How can I trigger handleChange() method ?

class App extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
    value: 'random text'
    }
  }
  handleChange (e) {
    console.log('handle change called')
  }
  handleClick () {
    this.setState({value: 'another random text'})
  }
  render () {
    return (
      <div>
        <input value={this.state.value} onChange={this.handleChange}/>
        <button onClick={this.handleClick.bind(this)}>Change Input</button>
      </div>
    )
  }
}

ReactDOM.render(<App />,  document.getElementById('app'))

Here is the codepen link: http://codepen.io/madhurgarg71/pen/qrbLjp

madhurgarg
  • 1,301
  • 2
  • 11
  • 17
  • As far as I understand is that you want to change the input only after the button has been clicked. Am I right?? – Shubham Khatri Mar 02 '17 at 09:34
  • 1
    @ShubhamKhatri Sorry I was wrong, I don't want to change only after the button has been clicked. It's just and example where handleClick is just setting the state. It has nothing to do with handleClick. Sorry I interpreted wrong. – madhurgarg Nov 06 '17 at 09:48
  • Did you figure this out? As far as I can tell, there's no reason why the above script won't work, at least in the latest React version. I just tried it in the provided Codepen snippet, & it works fine. – Shan Jan 15 '19 at 05:32
  • What you have on the codepen is different from what is shown here. This works fine, absolutely no reason why it's not working. You probably wanted to check on the console that 'handle change called' – KJ Sudarshan Feb 13 '21 at 16:49
  • Should use functional components – mercury Jun 03 '21 at 15:34

10 Answers10

44

You need to trigger the onChange event manually. On text inputs onChange listens for input events.

So in you handleClick function you need to trigger event like

handleClick () {
    this.setState({value: 'another random text'})
    var event = new Event('input', { bubbles: true });
    this.myinput.dispatchEvent(event);
  }

Complete code

class App extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
    value: 'random text'
    }
  }
  handleChange (e) {
    console.log('handle change called')
  }
  handleClick () {
    this.setState({value: 'another random text'})
    var event = new Event('input', { bubbles: true });
    this.myinput.dispatchEvent(event);
  }
  render () {
    return (
      <div>
        <input readOnly value={this.state.value} onChange={(e) => {this.handleChange(e)}} ref={(input)=> this.myinput = input}/>
        <button onClick={this.handleClick.bind(this)}>Change Input</button>
      </div>
    )
  }
}

ReactDOM.render(<App />,  document.getElementById('app'))

Codepen

Edit: As Suggested by @Samuel in the comments, a simpler way would be to call handleChange from handleClick if you don't need to the event object in handleChange like

handleClick () {
    this.setState({value: 'another random text'})
    this.handleChange();
  }

I hope this is what you need and it helps you.

Shubham Khatri
  • 270,417
  • 55
  • 406
  • 400
  • Using a ref on an element is almost always the wrong answer, unless you need to do something can't be done other ways. Here, a **much** simpler solution is to simply call handleChange from handleClick, as suggested in this answer: https://stackoverflow.com/a/42550931/2608603 –  Aug 24 '17 at 12:21
  • 1
    @SamuelScheiderich Certainly a simpler way if you don't want the event object of onChange in handleChange. I did not include it in the original answer because handleChange contained the event as an argument – Shubham Khatri Aug 24 '17 at 12:29
  • 2
    @ShubhamKhatri Sorry but I'm withdrawing my upvote. I just realised that your answer doesn't answer my question. What you are doing is calling handleChange method explicitly from handleClick. It's not about handleClick. React state doesn't know about handleClick method right? I'll give you a more practical example where we might need this. Let's say you have a login form and you saved the password. And when you come to the login page again your form input got filled by browser automatically. Now you want to invoke your handleChange method for some UI animation. – madhurgarg Nov 06 '17 at 09:38
  • 9
    Does not work at all as for 2019. Form now does not reacting to manual `dispatchEvent` for some reason – Limbo Apr 11 '19 at 14:53
  • @ShubhamKhatri, good day Shubham, great answer and I wanted to kindly ask you one question, Should we always use onChange handler for input fields. I was just creating calculator and decided to use e.g without using onChange. I just change state and then value of input also changes. Is it the right way? Or should I always use onChange whenever I work with input field?:) – Dickens Sep 23 '19 at 07:36
38

I tried the other solutions and nothing worked. This is because of input logic in React.js has been changed. For detail, you can see this link: https://hustle.bizongo.in/simulate-react-on-change-on-controlled-components-baa336920e04.

In short, when we change the value of input by changing state and then dispatch a change event then React will register both the setState and the event and consider it a duplicate event and swallow it.

The solution is to call native value setter on input (See setNativeValue function in following code)

Example Code

import React, { Component } from 'react'
export class CustomInput extends Component {

    inputElement = null;
    
    // THIS FUNCTION CALLS NATIVE VALUE SETTER
    setNativeValue(element, value) {
        const valueSetter = Object.getOwnPropertyDescriptor(element, 'value').set;
        const prototype = Object.getPrototypeOf(element);
        const prototypeValueSetter = Object.getOwnPropertyDescriptor(prototype, 'value').set;

        if (valueSetter && valueSetter !== prototypeValueSetter) {
            prototypeValueSetter.call(element, value);
        } else {
            valueSetter.call(element, value);
        }
    }


    constructor(props) {
        super(props);

        this.state = {
            inputValue: this.props.value,
        };
    }

    addToInput = (valueToAdd) => {
        this.setNativeValue(this.inputElement, +this.state.inputValue + +valueToAdd);
        this.inputElement.dispatchEvent(new Event('input', { bubbles: true }));
    };

    handleChange = e => {
        console.log(e);
        this.setState({ inputValue: e.target.value });
        this.props.onChange(e);
    };

    render() {
        return (
            <div>
                <button type="button" onClick={() => this.addToInput(-1)}>-</button>
                <input
                    readOnly
                    ref={input => { this.inputElement = input }}
                    name={this.props.name}
                    value={this.state.inputValue}
                    onChange={this.handleChange}></input>
                <button type="button" onClick={() => this.addToInput(+1)}>+</button>
            </div>
        )
    }
}

export default CustomInput

Result

enter image description here

Muhammad Inaam Munir
  • 1,221
  • 10
  • 18
  • Are `prototypeValueSetter.call` and `valueSetter.call` switched around? In the example above `valueSetter` will only be called if a) It is undefined or b) it is strictly equal to `prototypeValueSetter`. – nik10110 Jun 16 '21 at 19:32
  • I tested the code with them switched around - doesn't work. As a result I see no reason to not exclusively use `prototypeValueSetter` and delete all mentions of `valueSetter`. – nik10110 Jun 16 '21 at 19:41
  • Why is the `if()` condition necessary in `setNativeValue()`? In other words, couldn't we just do `Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, "value").set.call(element, value);`? (Please forgive the one-liner; it's hopefully easier to read a one-liner in comments) – rinogo Dec 24 '21 at 01:27
  • Thanks! your explanation was exactly what I was missing to understand my issue. Also, in my case, I just needed to add the ```dispatchEvent ``` to the button. – Thremulant Feb 02 '22 at 20:08
14

I think you should change that like so:

<input value={this.state.value} onChange={(e) => {this.handleChange(e)}}/>

That is in principle the same as onClick={this.handleClick.bind(this)} as you did on the button.

So if you want to call handleChange() when the button is clicked, than:

<button onClick={this.handleChange.bind(this)}>Change Input</button>

or

handleClick () {
  this.setState({value: 'another random text'});
  this.handleChange();
}
philipp
  • 15,947
  • 15
  • 61
  • 106
  • I want to trigger handleChange when button is clicked. Its working by manually typing inside the input. Suppose the input is readOnly. – madhurgarg Mar 02 '17 at 09:32
7

In a functional component you can do this, let's assume we have a input[type=number]

const MyInputComponent = () => {
    const [numberValue, setNumberValue] = useState(0);
    const numberInput = useRef(null);

    /**
    * Dispatch Event on Real DOM on Change
    */
    useEffect(() => {
        numberInput.current.dispatchEvent(
            new Event("change", {
                detail: {
                    newValue: numberValue,
                },
                bubbles: true,
                cancelable: true,
            })
        );
    }, [numberValue]);

    return (
        <>
            <input    
                type="number"            
                value={numberValue}             
                ref={numberInput}
                inputMode="numeric"
                onChange={(e) => setNumberValue(e.target.value)}
            />
        </>
    )
}
Amin
  • 1,637
  • 1
  • 22
  • 40
  • 1
    This does work but the options properties in the Event object is just two which are bubbles and cancelable. – Niyi Aromokeye Oct 20 '22 at 08:34
  • Yes you are right, I think it works because the value is already set via react, so the when listening to `change` event we can access the value on `event.target`, in this answer the `detail: { newValue: numberValue }` part is redundant and can be removed, or if you need extra information, `CustomEvent` should be used instead of `Event` which supports `detail` property – Amin Feb 21 '23 at 20:06
4

The other answers talked about direct binding in render hence I want to add few points regarding that.

You are not recommended to bind the function directly in render or anywhere else in the component except in constructor. Because for every function binding a new function/object will be created in webpack bundle js file hence the bundle size will grow. Your component will re-render for many reasons like when you do setState, new props received, when you do this.forceUpdate() etc. So if you directly bind your function in render it will always create a new function. Instead do function binding always in constructor and call the reference wherever required. In this way it creates new function only once because constructor gets called only once per component.

How you should do is something like below

constructor(props){
  super(props);
  this.state = {
    value: 'random text'
  }
  this.handleChange = this.handleChange.bind(this);
}

handleChange (e) {
  console.log('handle change called');
  this.setState({value: e.target.value});
}

<input value={this.state.value} onChange={this.handleChange}/>

You can also use arrow functions but arrow functions also does create new function every time the component re-renders in certain cases. You should know about when to use arrow function and when are not suppose to. For detailed explation about when to use arrow functions check the accepted answer here

Hemadri Dasari
  • 32,666
  • 37
  • 119
  • 162
  • 5
    This answers completely ignores the actual question. while the information presented in your answer is valuable *in general*, I do not think unrelated answers should exist, because it only creates clutter for people trying to find the actual **useful** which exactly (or closely) answers the topic – vsync Aug 13 '19 at 08:08
1

you must do 4 following step :

  1. create event

    var event = new Event("change",{
        detail: {
            oldValue:yourValueVariable,
            newValue:!yourValueVariable
        },
        bubbles: true,
        cancelable: true
    });
    event.simulated = true;
    let tracker = this.yourComponentDomRef._valueTracker;
    if (tracker) {
        tracker.setValue(!yourValueVariable);
    }
    
  2. bind value to component dom

    this.yourComponentDomRef.value = !yourValueVariable;
    
  3. bind element onchange to react onChange function

     this.yourComponentDomRef.onchange = (e)=>this.props.onChange(e);
    
  4. dispatch event

    this.yourComponentDomRef.dispatchEvent(event);
    

in above code yourComponentDomRef refer to master dom of your React component for example <div className="component-root-dom" ref={(dom)=>{this.yourComponentDomRef= dom}}>

javad bat
  • 4,236
  • 6
  • 26
  • 44
  • 6
    It's a mambo-jambo mess of incorrect voodoo collection of code that you cared not to explain. From my experience, only the `_valueTracker` is indeed necessary and the `event.simulated = true` but the rest makes no sense. **Why** would you do `value = !yourValueVariable` ? makes no sense at all. Might as well added `5. jump on your head and roll twice on your back` as part of the *"must do"* so-called steps. – vsync Jun 20 '20 at 20:38
1

Approach with React Native and Hooks:

You can wrap the TextInput into a new one that watches if the value changed and trigger the onChange function if it does.

import React, { useState, useEffect } from 'react';
import { View, TextInput as RNTextInput, Button } from 'react-native';

// New TextInput that triggers onChange when value changes.
// You can add more TextInput methods as props to it.
const TextInput = ({ onChange, value, placeholder }) => {

  // When value changes, you can do whatever you want or just to trigger the onChange function
  useEffect(() => {
    onChange(value);
  }, [value]);

  return (
    <RNTextInput
      onChange={onChange}
      value={value}
      placeholder={placeholder}
    />
  );
};

const Main = () => {

  const [myValue, setMyValue] = useState('');

  const handleChange = (value) => {
    setMyValue(value);
    console.log("Handling value");
  };

  const randomLetters = [...Array(15)].map(() => Math.random().toString(36)[2]).join('');

  return (
    <View>
      <TextInput
        placeholder="Write something here"
        onChange={handleChange}
        value={myValue}
      />
      <Button
        title='Change value with state'
        onPress={() => setMyValue(randomLetters)}
      />
    </View>
  );
};

export default Main;
0

I know what you mean, you want to trigger handleChange by click button.

But modify state value will not trigger onChange event, because onChange event is a form element event.

Xat_MassacrE
  • 147
  • 8
  • But how then its get triggered when you save your login credentials and browser's autofill sets the input value. – madhurgarg Mar 02 '17 at 09:58
  • Actually, i think there is no need to trigger that function, maybe you should just tell me what actions you want to do. – Xat_MassacrE Mar 02 '17 at 10:22
-1

I had a similar need and end up using componentDidMount(), that one is called long after component class constructor (where you can initialize state from props - as an exmple using redux )

Inside componentDidMount you can then invoke your handleChange method for some UI animation or perform any kind of component properties updates required.

As an example I had an issue updating an input checkbox type programatically, that's why I end up using this code, as onChange handler was not firing at component load:

   componentDidMount() {

    // Update checked 
    const checkbox = document.querySelector('[type="checkbox"]');

    if (checkbox) 
      checkbox.checked = this.state.isChecked;
  }

State was first updated in component class constructor and then utilized to update some input component behavior

mariano_c
  • 371
  • 2
  • 9
-2

Try this code if state object has sub objects like this.state.class.fee. We can pass values using following code:

this.setState({ class: Object.assign({}, this.state.class, { [element]: value }) }
Maciej Jureczko
  • 1,560
  • 6
  • 19
  • 23