Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why setState of react-select's menu selection making modal close state?

I have a modal which listens to the outside click of modal and triggers onclose method which closes the modal. Now I added react select to the modal, and after choosing one of the options it's making my modal go on close state. I am capturing the outside click from one of the medium articles.

function useOuterClickNotifier(onOuterClick, innerRef) {
  useEffect(() => {
    if (innerRef.current) {
      document.addEventListener("click", handleClick);
    }
    return () => document.removeEventListener("click", handleClick);

    function handleClick(e) {
      if (innerRef.current && !innerRef.current.contains(e.target)) {
        onOuterClick(e);
      }
    }
  }, [onOuterClick, innerRef]);
}

https://medium.com/@pitipatdop/little-neat-trick-to-capture-click-outside-with-react-hook-ba77c37c7e82

My index.js

import React, { useState } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
import Select from "react-select";
import Modal from "../Modal";

const options = [
  { value: "chocolate", label: "Chocolate" },
  { value: "strawberry", label: "Strawberry" },
  { value: "vanilla", label: "Vanilla" }
];

function App() {
  const [isOpen, setOpen] = useState(false);
  function onCloseModal() {
    console.log("Why closing?");
    setOpen(false);
  }

  function openModal() {
    setOpen(true);
  }
  return (
    <>
      {isOpen && (
        <Modal closeModal={onCloseModal}>
          <div className="card">
            <Select options={options} />
          </div>
        </Modal>
      )}
      <button onClick={openModal}>Open modal</button>
    </>
  );
}

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

How do I fix it?

https://codesandbox.io/s/usegooglemap-repro-x3q37?fontsize=14&hidenavigation=1&theme=dark

System info

System:
    OS: macOS 10.14.5
    CPU: (12) x64 Intel(R) Core(TM) i7-8850H CPU @ 2.60GHz
  Binaries:
    Node: 12.4.0 - ~/.nvm/versions/node/v12.4.0/bin/node
    Yarn: 1.16.0 - ~/.nvm/versions/node/v12.4.0/bin/yarn
    npm: 6.9.0 - ~/.nvm/versions/node/v12.4.0/bin/npm
  Browsers:
    Chrome: 78.0.3904.97
    Firefox: 69.0
    Safari: 12.1.1
  npmPackages:
    react: 16.11.0 => 16.11.0 
    react-dom: 16.11.0 => 16.11.0 
    react-scripts: 3.2.0 => 3.2.0 
like image 420
Subhendu Kundu Avatar asked Oct 15 '25 18:10

Subhendu Kundu


1 Answers

I believe that this is caused by the fact that the select menu has already been unmounted when the event listener of your hook is called. React does not know about click handlers attached manually so it will start updating the DOM immediately. By the time the event is handled by your listener the DOM node that was click has already been removed.

To prevent that you can pass true as a third parameter to addEventListener and removeEventListener. This parameter is useCapture. If true it will call your listener during the capturing phase before any listeners of the target elements.

From mdn:

useCapture [Optional]

A Boolean indicating whether events of this type will be dispatched to the registered listener before being dispatched to any EventTarget beneath it in the DOM tree. Events that are bubbling upward through the tree will not trigger a listener designated to use capture. Event bubbling and capturing are two ways of propagating events which occur in an element that is nested within another element, when both elements have registered a handle for that event. The event propagation mode determines the order in which elements receive the event. See DOM Level 3 Events and JavaScript Event order for a detailed explanation. If not specified, useCapture defaults to false.

Edit useGoogleMap repro

function useOuterClickNotifier(onOuterClick, innerRef) {
  useEffect(() => {
    if (innerRef.current) {
      document.addEventListener("click", handleClick, true);
    }
    return () => document.removeEventListener("click", handleClick, true);

    function handleClick(e) {
      if (innerRef.current && !innerRef.current.contains(e.target)) {
        onOuterClick(e);
      }
    }
  }, [onOuterClick, innerRef]);
}
like image 86
trixn Avatar answered Oct 17 '25 07:10

trixn