简体   繁体   中英

React: trigger onChange on checkbox input when changing checked state programmatically?

I have a ref to an <input type="checkbox"/> element, and when I programmatically set checked=false on the element, the element's onChange callback does not get called.

I tried using ref.dispatchEvent(new Event('input')) and ref.dispatchEvent(new Event('change')) and neither caused the React onChange callback to get executed.

All the questions and answers I could find on StackOverflow about this have to do with <input type="text"/> elements, none dealing with changing the checked property programmatically on an <input type="checkbox"/> element and its onChange handler not being invoked.

Here's a CodePen that demonstrates the issue:

https://codepen.io/dossy/pen/QWKVNzZ/left/?editors=0011

You can check and uncheck the checkbox, and the <div>Checked!</div> will appear and disappear as expected. However, clicking the <button>Reset</button> will uncheck the checkbox if it's checked, but since the input 's onChange handler isn't being executed, the div isn't being hidden as it should be.

...

Yes, I know that I could do this as a Controlled Component but that's not the point: I have a use case where using refs is required so I must implement this as an Uncontrolled Component , and getting the onChange handler to execute when the DOM element changes is the problem I need to solve.

Thanks!

It's better to do things the "react way".

That means, instead of manipulating dom elements with imperative code (if you're using refs, you're using imperative code), do it declaratively with state/props:

function App() {
  const [checked,setChecked] = React.useState(false);
  return (
    <div className="h-screen flex bg-white text-gray-900 justify-center items-center">
      <div className="flex items-start w-64">
        <div className="flex items-center gap-4">
          <button
            className="inline-flex items-center px-4 py-2 border border-gray-500 rounded-md"
            onClick={() => setChecked(false)}
          >
            Reset
          </button>
          <div>Checkbox:</div>
          <input
            type="checkbox"
            checked={checked}
            onChange={() => setChecked(!checked)}
            className="h-4 w-4 border-gray-300 rounded"
          />
          <div className={checked ? '' : 'hidden'}>
            Checked!
          </div>
        </div>
      </div>
    </div>
  );
}

ReactDOM.render(<App />, document.getElementById("root"));

Here's a link to the updated pen: https://codepen.io/zuze-lab/pen/QWKVEbY?editors=0011

EDIT: I want to be really clear, ref's aren't bad, not at all. Lots of react libraries expose refs because imperative code makes sense for those libraries APIs. When using refs to add event listeners, or imperatively manipulate elements, you're doing things wrong and you need to back up.

Here is the working code. working link https://codesandbox.io/s/blissful-wozniak-cc1sn?file=/src/Test.js:0-1753

import React, { useEffect, useRef } from "react";
export function Test() {
  const ref_input = useRef(null);
  const ref_text = useRef(null);
  useEffect(() => {
    ref_input.current.addEventListener("change", function (event) {
      alert(event.target.checked);
    });
  }, []);
  function triggerEvent(element, eventName) {
    var event = document.createEvent("HTMLEvents");
    event.initEvent(eventName, false, true);
    element.dispatchEvent(event);
  }

  return (
    <div className="h-screen flex bg-white text-gray-900 justify-center items-center">
      <div className="flex items-start w-64">
        <div className="flex items-center gap-4">
          <button
            className="inline-flex items-center px-4 py-2 border border-gray-500 rounded-md"
            onClick={() => {
              ref_input.current.checked = false;
              triggerEvent(ref_input.current, "change");
              //ref_input.dispatchEvent(new Event("input"));
              //ref_input.current.dispatchEvent(new Event("onChange"));
            }}
          >
            Reset
          </button>
          <div>Checkbox:</div>
          <input
            ref={ref_input}
            type="checkbox"
            className="h-4 w-4 border-gray-300 rounded"
            // onChange={(e) => {
            //   console.log("onChange called", e.target.checked);
            //   e.target.checked
            //     ? ref_text.current.classList.remove("hidden")
            //     : ref_text.current.classList.add("hidden");
            // }}
          />
          <div ref={ref_text} className="hidden">
            Checked!
          </div>
        </div>
      </div>
    </div>
  );
}

ReactDOM.render(<App />, document.getElementById("root"));

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM