反应: 触发器 onChange 如果输入值按状态改变?

编辑: 我不想只在按钮被单击时才调用 handleChange。它与 handleClick 无关。我在@shubhakhatri 的回答中举了一个例子。

我想改变输入值根据状态,值是改变,但它不会触发 handleChange()方法。如何触发 handleChange()方法?

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'))

下面是 codepen 链接: http://codepen.io/madhurgarg71/pen/qrbLjp

437208 次浏览

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();
}

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.

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.

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}}>

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

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 }) }

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

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

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;

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)}
/>
</>
)
}