Обработчик события get вызывается до установки обработчика - PullRequest
2 голосов
/ 02 апреля 2020

У меня есть этот повторно используемый компонент в моем приложении реакции.

export const OutsideWrapper = ({ children, onOutside, className }) => {
  const wrapperRef = useRef(null);
  const [style, setStyles] = useState({
    opacity: 1
  });
  useEffect(() => {
    console.log("1. component was mounted");
    const i = e => {
      if (wrapperRef.current && !wrapperRef.current.contains(e.target)) {
        console.log("3. outside click was trigerred");
        e.preventDefault();
        setStyles({ opacity: 0 });
        setTimeout(() => {
          onOutside();
        }, 100);
      }
    };
    window.addEventListener("click", i, true);
    console.log('2. listener was added');
    return () => {
      console.log("4. listerner was removed");
      window.removeEventListener("click", i, true);
    };
  }, [onOutside]);
  return (
    <div
      ref={wrapperRef}
      style={style}
      className={`outside-wrapper ${className}`}
    >
      {children}
    </div>
  );
};

Когда этот компонент OutsideWrapper будет обработан, он должен добавить слушатель события в документ, затем прослушать событие, вызвать функцию onOutside и затем размонтировать. (onOutside размонтирует компонент). После того, как этот слушатель удаляется.

Но когда компонент get рендерится, он немедленно вызывает onOutside и размонтирует.

Вот часть родительского компонента:

const [down, setDown] = useState(false);
return (
    <input onFocus={()=>setDown(true)}/>
       {down && (
          <OutsideWrapper
             onOutside={() => setDown(false)}
             className="input-wrapper"
           >
            <DropDownList
              items={dropDownItems}
              term={data.location}
              onChoose={onChoose}
             />
          </OutsideWrapper>
       )}
    )

1 Ответ

1 голос
/ 02 апреля 2020

Вызов window.addEventListener внутри useEffect вызывается, когда React рендерит компонент, что происходит на focus . Событие, которое вызывает фокус, это не щелчок, а mousedown. Когда происходит следующее mouseup, генерируется событие щелчка, которое перехватывается. Обратите внимание, что если вы вкладываете вкладку в фокус ввода, это не вызывает ошибку.

Существует несколько подходов, чтобы исправить это, но я рекомендую игнорировать события щелчка, которые происходят на самом входе.

Вот пример: я добавил ссылку в <input>, передал ее в OutsideWrapper и добавил проверку, как у вас, для wrapperRef для новой ссылки.

function Test() {
  const [down, setDown] = React.useState(false);
  const focusRef = React.useRef();
  return (
    <div className="App">
      <h1>Hello CodeSandbox</h1>
      <h2>
        <input ref={focusRef} onFocus={() => setDown(true)} />
        {down && (
          <OutsideWrapper
            onOutside={() => setDown(false)}
            focusedRef={focusRef}
            className="input-wrapper"
          >
            children
          </OutsideWrapper>
        )}
      </h2>
    </div>
  );
}

const OutsideWrapper = ({ children, onOutside, className, focusedRef }) => {
  const wrapperRef = React.useRef(null);
  const [style, setStyles] = React.useState({
    opacity: 1
  });
  React.useEffect(() => {
    console.log("1. component was mounted");
    const i = e => {
      console.log(e.target, wrapperRef.current);
      if (
        wrapperRef.current &&
        !wrapperRef.current.contains(e.target) &&
        focusedRef.current !== e.target
      ) {
        console.log("3. outside click was trigerred");
        e.preventDefault();
        setStyles({ opacity: 0 });
        setTimeout(() => {
          onOutside();
        }, 100);
      }
    };
    window.addEventListener("click", i, true);
    console.log("2. listener was added");
    return () => {
      console.log("4. listerner was removed");
      window.removeEventListener("click", i, true);
    };
  }, [onOutside, focusedRef]);
  return (
    <div
      ref={wrapperRef}
      style={style}
      className={`outside-wrapper ${className}`}
    >
      {children}
    </div>
  );
};
...