Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React - Cannot dispatch onChange event from input text element programatically

I have two components: ParentComponent and ChildComponent.

The ChildComponent has an input[type="text"] element that when it changes its text that event is propagated to the ParentComponent through the event change and the onChange listener.

The code below is a simplification of a bigger problem, that's why you will see some requirements highlighted there.

My problem is that I need to trigger the change event inside the function: handleClick. I did some experiments with no luck.

Here you have the code sandbox you can experiment with (please provide a fork with your approach):

https://codesandbox.io/s/wqw49j5krw

Here you have the code:

ParentComponent.js

import React from "react";
import ChildComponent from "./ChildComponent";

export default class ParentComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      name: "Peter"
    };
  }
  handleChange = event => {
    let target = event.target;
    let value = target.value;
    this.setState({
      name: value
    });
  };
  render() {
    return (
      <div>
        <ChildComponent value={this.state.name} onChange={this.handleChange} /><br />
        <span>Hello</span>&nbsp;
        <span>{this.state.name}!</span>
      </div>
    );
  }
}

ChildComponent.js

import React from "react";
import ReactDOM from "react-dom";

export default class ChildComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      value: this.props.value
    };
  }
  handleChange = event => {
    const name = event.target.value;
    this.setState({ value: name });
    if (this.props.onChange !== undefined) {
      this.props.onChange(event);
    }
  };
  handleClick = name => {
    const inputName = this.refs.name;
    console.log('Name before being changed: ' + inputName.value); // this works
    // PROBABLY HERE IS WHERE THE CODE NEEDS TO BE MODIFIED
    this.setState({ value: name });
    var event = new Event('input', { bubbles: true });
    inputName.dispatchEvent(event); // this doesn't propagate the event to the parent
  };
  render() {
    return (
      <div>
        {"Your name: "}
        <input type="text"
          value={this.state.value}
          onChange={this.handleChange}
          ref="name"
        />
        {/* requirement: the following 2 lines cannot be modified */}
        <button onClick={e => { this.handleClick("Steve"); }}>Steve</button>
        <button onClick={e => { this.handleClick("Emily"); }}>Emily</button>
      </div>
    );
  }
}

Any idea on how to get this working?

Thanks!

like image 946
Viewsonic Avatar asked Sep 06 '25 03:09

Viewsonic


1 Answers

You are missing the track of the input change,

Because React tracks when you set the value property on an input to keep track of the node's value. When you dispatch a change event, it checks it's last value against the current value and if they're the same it does not call any event handlers (as no change has taken place as far as react is concerned). So we have to set the value in a way that React's value setter function will not be called, which is where the setNativeValue comes into play.

Here you are setting the state instead of changing the input's value directly so, it will not get the updated value when you are dispatching the event. and if you write value directly like input.value it can not track the changes of the input.

so, you should set the value and dispatch the event, this way you can have the updated value when event is dispatched.

Here is the link of the reference and another, there are other ways too, to fire the change event.

Here is the function you need to set the property so that react can track the changes,

  handleClick = name => {
    const inputName = this.refs.name;
    console.log("Name before being changed: " + inputName.value); // this works

    var event = new Event("input", { bubbles: true });

    this.setNativeValue(inputName, name);
    inputName.dispatchEvent(event);
  };

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

Demo

like image 161
Just code Avatar answered Sep 07 '25 23:09

Just code