web-dev-qa-db-ja.com

Reactコンポーネントの外側のクリックを検出

この 記事 で説明されているように、clickイベントがコンポーネントの外で起こったかどうかを検出する方法を探しています。 clickイベントからのターゲットがdom要素をその親の1つとして持っているかどうかを確認するためにjQuery nearest()が使用されます。一致がある場合、clickイベントはいずれかの子に属しているため、コンポーネントの外部にあるとは見なされません。

だから私のコンポーネントで私はウィンドウにクリックハンドラを添付したいです。ハンドラが起動したら、ターゲットと自分のコンポーネントのdomの子を比較する必要があります。

Clickイベントには、 "path"のようなプロパティが含まれています。これは、イベントが移動したDOMパスを保持しているようです。何を比較するのか、またはどのようにしてそれを最もよくトラバースするのかわからないし、誰かがすでにそれを巧妙な効用関数に入れているにちがいないと思います...

241

次の解決策はES6を使用し、メソッドを介してrefを設定するだけでなくバインドのためのベストプラクティスに従います。

実際にそれを見るには: コードサンドボックスのデモ

import React, { Component } from 'react';
import PropTypes from 'prop-types';

/**
 * Component that alerts if you click outside of it
 */
export default class OutsideAlerter extends Component {
  constructor(props) {
    super(props);

    this.setWrapperRef = this.setWrapperRef.bind(this);
    this.handleClickOutside = this.handleClickOutside.bind(this);
  }

  componentDidMount() {
    document.addEventListener('mousedown', this.handleClickOutside);
  }

  componentWillUnmount() {
    document.removeEventListener('mousedown', this.handleClickOutside);
  }

  /**
   * Set the wrapper ref
   */
  setWrapperRef(node) {
    this.wrapperRef = node;
  }

  /**
   * Alert if clicked on outside of element
   */
  handleClickOutside(event) {
    if (this.wrapperRef && !this.wrapperRef.contains(event.target)) {
      alert('You clicked outside of me!');
    }
  }

  render() {
    return <div ref={this.setWrapperRef}>{this.props.children}</div>;
  }
}

OutsideAlerter.propTypes = {
  children: PropTypes.element.isRequired,
};
375
Ben Bud

これは、イベントをコンテナに添付しなくても私にとって最も効果的な解決策です。

入力要素など、特定のHTML要素には " focus "と呼ばれるものがあります。それらがその焦点を失うとき、それらの要素はまた blur イベントに反応するでしょう。

どの要素にもフォーカスを持たせるには、そのtabindex属性が-1以外に設定されていることを確認してください。通常のHTMLではtabindex属性を設定することになりますが、ReactではtabIndexを使用する必要があります(大文字のIに注意してください)。

element.setAttribute('tabindex',0)を使ってJavaScriptでそれを行うこともできます

これは私がカスタムDropDownメニューを作るために使っていたものです。

var DropDownMenu = React.createClass({
    getInitialState: function(){
        return {
            expanded: false
        }
    },
    expand: function(){
        this.setState({expanded: true});
    },
    collapse: function(){
        this.setState({expanded: false});
    },
    render: function(){
        if(this.state.expanded){
            var dropdown = ...; //the dropdown content
        } else {
            var dropdown = undefined;
        }

        return (
            <div className="dropDownMenu" tabIndex="0" onBlur={ this.collapse } >
                <div className="currentValue" onClick={this.expand}>
                    {this.props.displayValue}
                </div>
                {dropdown}
            </div>
        );
    }
});
120

ここで多くの方法を試した後、私は github.com/Pomax/react-onclickoutside を使用することにしました。

私はnpm経由でモジュールをインストールして私のコンポーネントにインポートしました:

import onClickOutside from 'react-onclickoutside'

それから、私のコンポーネントクラスでhandleClickOutsideメソッドを定義しました:

handleClickOutside = () => {
  console.log('onClickOutside() method called')
}

そして私のコンポーネントをエクスポートするとき、私はそれをonClickOutside()でラップしました:

export default onClickOutside(NameOfComponent)

それでおしまい。

83
Beau Smith

私は同じ問題で立ち往生していました。私はここのパーティーに少し遅れています、しかし私にとってこれは本当に良い解決策です。うまくいけば、それは他の誰かに役立つだろう。 react-domからfindDOMNodeをインポートする必要があります

import ReactDOM from 'react-dom';
// ... ✂

componentDidMount() {
    document.addEventListener('click', this.handleClickOutside, true);
}

componentWillUnmount() {
    document.removeEventListener('click', this.handleClickOutside, true);
}

handleClickOutside = event => {
    const domNode = ReactDOM.findDOMNode(this);

    if (!domNode || !domNode.contains(event.target)) {
        this.setState({
            visible: false
        });
    }
}

React Hooks Approach(16.8 +)

useComponentVisibleと呼ばれる再利用可能なフックを作成することができます。

import { useState, useEffect, useRef } from 'react';

export default function useComponentVisible(initialIsVisible) {
    const [isComponentVisible, setIsComponentVisible] = useState(initialIsVisible);
    const ref = useRef(null);

    const handleClickOutside = (event) => {
        if (ref.current && !ref.current.contains(event.target)) {
            setIsComponentVisible(false);
        }
    };

    useEffect(() => {
        document.addEventListener('click', handleClickOutside, true);
        return () => {
            document.removeEventListener('click', handleClickOutside, true);
        };
    });

    return { ref, isComponentVisible, setIsComponentVisible };
}

次に、コンポーネントに次の機能を追加します。

const DropDown = () => {
    const { ref, isComponentVisible } = useComponentVisible(true);
    return (
       <div ref={ref}>
          {isComponentVisible && (<p>Dropdown Component</p>)}
       </div>
    );

}

ここで codesandbox の例を見つけてください。

64
Paul Fitzgerald

Discussion.reactjs.org でBen Alpertのおかげで解決策が見つかりました。提案されたアプローチはドキュメントにハンドラを添付しますが、それは問題があることが判明しました。ツリー内のコンポーネントの1つをクリックすると表示が更新され、クリックした要素が更新されました。 Reactからの再レンダリングはドキュメントボディハンドラが呼び出される前に)起こるので、要素はツリーの「内側」として検出されませんでした。

これに対する解決策は、アプリケーションのルート要素にハンドラを追加することでした。

メイン:

window.__myapp_container = document.getElementById('app')
React.render(<App/>, window.__myapp_container)

成分:

import { Component, PropTypes } from 'react';
import ReactDOM from 'react-dom';

export default class ClickListener extends Component {

  static propTypes = {
    children: PropTypes.node.isRequired,
    onClickOutside: PropTypes.func.isRequired
  }

  componentDidMount () {
    window.__myapp_container.addEventListener('click', this.handleDocumentClick)
  }

  componentWillUnmount () {
    window.__myapp_container.removeEventListener('click', this.handleDocumentClick)
  }

  /* using fat arrow to bind to instance */
  handleDocumentClick = (evt) => {
    const area = ReactDOM.findDOMNode(this.refs.area);

    if (!area.contains(evt.target)) {
      this.props.onClickOutside(evt)
    }
  }

  render () {
    return (
      <div ref='area'>
       {this.props.children}
      </div>
    )
  }
}
38

ここで他の答えのどれも私のために働きませんでした。ぼやけてポップアップを隠そうとしていましたが、内容が完全に配置されていたため、内部の内容をクリックしてもonBlurが発生していました。

これが私にとって効果的なアプローチです。

// Inside the component:
onBlur(event) {
    // currentTarget refers to this component.
    // relatedTarget refers to the element where the user clicked (or focused) which
    // triggered this event.
    // So in effect, this condition checks if the user clicked outside the component.
    if (!event.currentTarget.contains(event.relatedTarget)) {
        // do your thing.
    }
},

お役に立てれば。

27
Niyaz

React Hooks (16.8 +)を使用した再利用可能なソリューション

Codesandbox

外部クリック通知フックを作成します。

function useOuterClickNotifier(onOuterClick, innerRef) {
  useEffect(
    () => {
      // only add listener, if the element exists
      if (innerRef.current) {
        document.addEventListener("click", handleClick);
      }

      // unmount previous first in case inputs have changed
      return () => document.removeEventListener("click", handleClick);

      function handleClick(e) {
        innerRef.current && !innerRef.current.contains(e.target) && onOuterClick(e);
      }
    },
    [onOuterClick, innerRef] // invoke again, if inputs have changed
  );
}

次のようなコンポーネントでフックを使用します。

const InnerComp = () => {
  const innerRef = useRef(null);
  useOuterClickNotifier(
    // if you want to optimize performance a bit,
    // don't provide an anonymous function here
    // See link down under (*1)
    e => alert("clicked outside of this component!"),
    innerRef
  );
  return (
    <div ref={innerRef}>
      inside component
    </div>
  );
}

* 1 エフェクトのスキップによるパフォーマンスの最適化

次に、ユースケースに応じて、外側のクリックコールバックで何かを行うことができます。これは、alert("clicked outside of this component!")で簡単にするためにここにスタブされています。例えば。 useStateフックで状態を設定するか、特定のコールバックを呼び出します。


クラスソリューションを超える利点:

  • 外部クリックの副作用ロジックをカプセル化して( 関心の分離 )、コンポーネントの消費を軽減できます。
  • useOuterClickNotifierフックは、クラスソリューションのようなラッパーコンポーネント/レンダリングプロップを必要とせずに、すべてのコンポーネントで再利用可能です( Link )。
  • 長期的には、Reactチームは、フックがReactコンポーネント( Link )を記述する主要な方法であると考えています。

短所:

  • 他のクラスコンポーネント内でuseOuterClickNotifierを使用することはできません( Link )。 here も参照してください。

追加情報:IOSクリック可能な要素の癖(mousedownclickに影響)

IOSでは、特定の要素のみがクリック可能として扱われます。詳細については、 quirksmodeSO answer 、および here を参照してください。この動作を回避するには、別の外部クリックリスナ要素を選択します(bodyを含む上向きのものはありません)。例えば。代わりに反応ルート<div id="root"></div>のクリックを登録し、その高さをビューポート全体に拡張することができます( IOS Codesandbox をご覧ください)。または、より良く、より反応的な方法:グローバルを完全に避け、外部のクリックを登録するために使用できるuseOuterClickNotifier Hookに明示的な要素を渡します。

ヒントについては@Omarに、CSSメディアクエリの代替案については@Kostas Sarantopoulosに感謝します(コメントを参照)。

希望、それが役立ちます。

22
ford04

[Update]React ^ 16.8 _ フックを使った解決策

CodeSandbox

import React, { useEffect, useRef, useState } from 'react';

const SampleComponent = () => {
    const [clickedOutside, setClickedOutside] = useState(false);
    const myRef = useRef();

    const handleClickOutside = e => {
        if (!myRef.current.contains(e.target)) {
            setClickedOutside(true);
        }
    };

    const handleClickInside = () => setClickedOutside(false);

    useEffect(() => {
        document.addEventListener('mousedown', handleClickOutside);
        return () => document.removeEventListener('mousedown', handleClickOutside);
    });

    return (
        <button ref={myRef} onClick={handleClickInside}>
            {clickedOutside ? 'Bye!' : 'Hello!'}
        </button>
    );
};

export default SampleComponent;

React ^ 16.3を使った解法:

CodeSandbox

import React, { Component } from "react";

class SampleComponent extends Component {
  state = {
    clickedOutside: false
  };

  componentDidMount() {
    document.addEventListener("mousedown", this.handleClickOutside);
  }

  componentWillUnmount() {
    document.removeEventListener("mousedown", this.handleClickOutside);
  }

  myRef = React.createRef();

  handleClickOutside = e => {
    if (!this.myRef.current.contains(e.target)) {
      this.setState({ clickedOutside: true });
    }
  };

  handleClickInside = () => this.setState({ clickedOutside: false });

  render() {
    return (
      <button ref={this.myRef} onClick={this.handleClickInside}>
        {this.state.clickedOutside ? "Bye!" : "Hello!"}
      </button>
    );
  }
}

export default SampleComponent;
14
onoya

これが私のアプローチです(demo - https://jsfiddle.net/agymay93/4/ ):

私はWatchClickOutsideと呼ばれる特別なコンポーネントを作成しました、そしてそれは(私はJSX構文を仮定します)のように使うことができます:

<WatchClickOutside onClickOutside={this.handleClose}>
  <SomeDropdownEtc>
</WatchClickOutside>

これがWatchClickOutsideコンポーネントのコードです。

import React, { Component } from 'react';

export default class WatchClickOutside extends Component {
  constructor(props) {
    super(props);
    this.handleClick = this.handleClick.bind(this);
  }

  componentWillMount() {
    document.body.addEventListener('click', this.handleClick);
  }

  componentWillUnmount() {
    // remember to remove all events to avoid memory leaks
    document.body.removeEventListener('click', this.handleClick);
  }

  handleClick(event) {
    const {container} = this.refs; // get container that we'll wait to be clicked outside
    const {onClickOutside} = this.props; // get click outside callback
    const {target} = event; // get direct click event target

    // if there is no proper callback - no point of checking
    if (typeof onClickOutside !== 'function') {
      return;
    }

    // if target is container - container was not clicked outside
    // if container contains clicked target - click was not outside of it
    if (target !== container && !container.contains(target)) {
      onClickOutside(event); // clicked outside - fire callback
    }
  }

  render() {
    return (
      <div ref="container">
        {this.props.children}
      </div>
    );
  }
}
5
pie6k

これはすでに多くの答えを持っていますが、それらはe.stopPropagation()を扱っていて、あなたが閉じたい要素の外側の反応リンクをクリックするのを防ぎません。

Reactがそれ自身の人工的なイベントハンドラを持っているという事実のためにあなたはイベントリスナーのためのベースとしてdocumentを使うことができません。 Reactは文書そのものを使っているので、これの前にe.stopPropagation()する必要があります。あなたが代わりに例えばdocument.querySelector('body')を使うならば。 Reactリンクからのクリックを防ぐことができます。以下は、click outsideとcloseを実装する方法の例です。
これは ES6 および React 16.3 を使用します。

import React, { Component } from 'react';

class App extends Component {
  constructor(props) {
    super(props);

    this.state = {
      isOpen: false,
    };

    this.insideContainer = React.createRef();
  }

  componentWillMount() {
    document.querySelector('body').addEventListener("click", this.handleClick, false);
  }

  componentWillUnmount() {
    document.querySelector('body').removeEventListener("click", this.handleClick, false);
  }

  handleClick(e) {
    /* Check that we've clicked outside of the container and that it is open */
    if (!this.insideContainer.current.contains(e.target) && this.state.isOpen === true) {
      e.preventDefault();
      e.stopPropagation();
      this.setState({
        isOpen: false,
      })
    }
  };

  togggleOpenHandler(e) {
    e.preventDefault();

    this.setState({
      isOpen: !this.state.isOpen,
    })
  }

  render(){
    return(
      <div>
        <span ref={this.insideContainer}>
          <a href="#open-container" onClick={(e) => this.togggleOpenHandler(e)}>Open me</a>
        </span>
        <a href="/" onClick({/* clickHandler */})>
          Will not trigger a click when inside is open.
        </a>
      </div>
    );
  }
}

export default App;
3
Richard Herries

絶対配置を必要とする人のために、私が選んだ簡単なオプションは、透明な背景でページ全体を覆うようにスタイルされたラッパーコンポーネントを追加することです。その後、この要素にonClickを追加して、内部コンポーネントを閉じることができます。

<div style={{
        position: 'fixed',
        top: '0', right: '0', bottom: '0', left: '0',
        zIndex: '1000',
      }} onClick={() => handleOutsideClick()} >
    <Content style={{position: 'absolute'}}/>
</div>

今のところコンテンツにクリックハンドラを追加すると、イベントも上位divに伝播されるため、handlerOutsideClickがトリガされます。これが望ましい動作ではない場合は、単にハンドラでイベントの進行を止めます。

<Content style={{position: 'absolute'}} onClick={e => {
                                          e.stopPropagation();
                                          desiredFunctionCall();
                                        }}/>

`

3
Anthony Garant

Ben Bud によって作られた受け入れられた答えを拡張するために、スタイル化されたコンポーネントを使っているならば、そのようにrefを渡すことはあなたにあなたにエラーを与えるでしょう。

コメント内で、スタイル付きコンポーネントをdivで囲み、そこに参照を渡すための推奨される修正が機能します。とは言っても、彼らの docs 彼らはすでにこの理由とスタイル付きコンポーネント内のrefの適切な使用法について説明しています。

Refプロップをスタイル付きコンポーネントに渡すと、StyledComponentラッパーのインスタンスが得られますが、基になるDOMノードには渡されません。これはrefがどのように機能するかによるものです。フォーカスのように、DOMメソッドを直接ラッパーに呼び出すことはできません。実際のラップされたDOMノードへの参照を取得するには、代わりにinnerRefプロップにコールバックを渡します。

そのようです:

<StyledDiv innerRef={el => { this.el = el }} />

それから、 "handleClickOutside"関数内で直接アクセスすることができます。

handleClickOutside = e => {
    if (this.el && !this.el.contains(e.target)) {
        console.log('clicked outside')
    }
}

これは "onBlur"アプローチにも当てはまります。

componentDidMount(){
    this.el.focus()
}
blurHandler = () => {
    console.log('clicked outside')
}
render(){
    return(
        <StyledDiv
            onBlur={this.blurHandler}
            tabIndex="0"
            innerRef={el => { this.el = el }}
        />
    )
}
3
Flatlinediver

私は このモジュールを使った (作者とは何の関連もない)

npm install react-onclickout --save
const ClickOutHandler = require('react-onclickout');
 
class ExampleComponent extends React.Component {
 
  onClickOut(e) {
    if (hasClass(e.target, 'ignore-me')) return;
    alert('user clicked outside of the component!');
  }
 
  render() {
    return (
      <ClickOutHandler onClickOut={this.onClickOut}>
        <div>Click outside of me!</div>
      </ClickOutHandler>
    );
  }
}

それはうまく仕事をしました。

3
ErichBSchulz
componentWillMount(){

  document.addEventListener('mousedown', this.handleClickOutside)
}

handleClickOutside(event) {

  if(event.path[0].id !== 'your-button'){
     this.setState({showWhatever: false})
  }
}

最後にクリックされたアイテムはイベントpath[0]です

3
Ivan Sanchez

他のすべての答えに対する私の最大の懸念は、クリックイベントをルートまたは親から下にフィルタリングする必要があることです。最も簡単な方法は、兄弟要素をposition:fixed、ドロップダウンの後ろのz-index 1に設定し、同じコンポーネント内のfixed要素でclickイベントを処理することです。すべてを特定のコンポーネントに集中管理します。

コード例

#HTML
<div className="parent">
  <div className={`dropdown ${this.state.open ? open : ''}`}>
    ...content
  </div>
  <div className="outer-handler" onClick={() => this.setState({open: false})}>
  </div>
</div>

#SASS
.dropdown {
  display: none;
  position: absolute;
  top: 0px;
  left: 0px;
  z-index: 100;
  &.open {
    display: block;
  }
}
.outer-handler {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    opacity: 0;
    z-index: 99;
    display: none;
    &.open {
      display: block;
    }
}
3
metroninja

私は部分的に これ に従うことによって、そして反応を必要とする参照の取り扱いに関するReactの公式文書に従うことによってこれを行いました^ 16.3。これは私がここで他の提案のいくつかを試みた後働いた唯一のものです...

class App extends Component {
  constructor(props) {
    super(props);
    this.inputRef = React.createRef();
  }
  componentWillMount() {
    document.addEventListener("mousedown", this.handleClick, false);
  }
  componentWillUnmount() {
    document.removeEventListener("mousedown", this.handleClick, false);
  }
  handleClick = e => {
    if (this.inputRef.current === e.target) {
      return;
    }
    this.handleclickOutside();
  };
handleClickOutside(){
...***code to handle what to do when clicked outside***...
}
render(){
return(
<div>
...***code for what's outside***...
<span ref={this.inputRef}>
...***code for what's "inside"***...
</span>
...***code for what's outside***
)}}
2
RawCode

戦略の例

私はコンポーネントの周りにラッパーを作成することによって同じことをするのに使う提供された解決策が好きです。

これはもっと行動的な振る舞いなので、私は戦略について考え、次のことを思いつきました。

私はReactを使い始めたばかりで、ユースケースの定型文を保存するために少し手助けが必要です。

見直して、あなたがどう思うか教えてください。

ClickOutsideBehavior

import ReactDOM from 'react-dom';

export default class ClickOutsideBehavior {

  constructor({component, appContainer, onClickOutside}) {

    // Can I extend the passed component's lifecycle events from here?
    this.component = component;
    this.appContainer = appContainer;
    this.onClickOutside = onClickOutside;
  }

  enable() {

    this.appContainer.addEventListener('click', this.handleDocumentClick);
  }

  disable() {

    this.appContainer.removeEventListener('click', this.handleDocumentClick);
  }

  handleDocumentClick = (event) => {

    const area = ReactDOM.findDOMNode(this.component);

    if (!area.contains(event.target)) {
        this.onClickOutside(event)
    }
  }
}

使用例

import React, {Component} from 'react';
import {APP_CONTAINER} from '../const';
import ClickOutsideBehavior from '../ClickOutsideBehavior';

export default class AddCardControl extends Component {

  constructor() {
    super();

    this.state = {
      toggledOn: false,
      text: ''
    };

    this.clickOutsideStrategy = new ClickOutsideBehavior({
      component: this,
      appContainer: APP_CONTAINER,
      onClickOutside: () => this.toggleState(false)
    });
  }

  componentDidMount () {

    this.setState({toggledOn: !!this.props.toggledOn});
    this.clickOutsideStrategy.enable();
  }

  componentWillUnmount () {
    this.clickOutsideStrategy.disable();
  }

  toggleState(isOn) {

    this.setState({toggledOn: isOn});
  }

  render() {...}
}

ノート

私は渡されたcomponentライフサイクルフックを保存し、これに似た方法でそれらをオーバーライドすることを考えました:

const baseDidMount = component.componentDidMount;

component.componentDidMount = () => {
  this.enable();
  baseDidMount.call(component)
}

componentは、ClickOutsideBehaviorのコンストラクターに渡されるコンポーネントです。
これにより、この動作のユーザーから有効/無効の定型句が削除されますが、見栄えはあまりよくありませんが

2
kidroca

'focus' ソリューションをイベントリスナーのドロップダウンに対して機能させるには、 onClick の代わりに onMouseDown eventを使用して追加できます。このようにしてイベントは発生し、その後ポップアップは以下のように閉じます。

<TogglePopupButton
                    onClick = { this.toggleDropup }
                    tabIndex = '0'
                    onBlur = { this.closeDropup }
                />
                { this.state.isOpenedDropup &&
                <ul className = { dropupList }>
                    { this.props.listItems.map((item, i) => (
                        <li
                            key = { i }
                            onMouseDown = { item.eventHandler }
                        >
                            { item.itemName}
                        </li>
                    ))}
                </ul>
                }
0
idtmc

私は以下の記事からこれを見つけました:

render(){return({this.node = node;}}> Popoverの切り替え{this.state.popupVisible &&(私はpopoverです!)}); }}

"Reactコンポーネントの外側でクリックを処理する" https://larsgraubner.com/handle-outside-clicks-react/ /

0
Amanda Koster

UseOnClickOutsideフック-React 16.8 +

一般的なuseOnOutsideClick関数を作成する

export const useOnOutsideClick = handleOutsideClick => {
  const innerBorderRef = useRef();

  const onClick = event => {
    if (
      innerBorderRef.current &&
      !innerBorderRef.current.contains(event.target)
    ) {
      handleOutsideClick();
    }
  };

  useMountEffect(() => {
    document.addEventListener("click", onClick, true);
    return () => {
      document.removeEventListener("click", onClick, true);
    };
  });

  return { innerBorderRef };
};

const useMountEffect = fun => useEffect(fun, []);

次に、機能コンポーネントでフックを使用します。

const OutsideClickDemo = ({ currentMode, changeContactAppMode }) => {

  const [open, setOpen] = useState(false);
  const { innerBorderRef } = useOnOutsideClick(() => setOpen(false));

  return (
    <div>
      <button onClick={() => setOpen(true)}>open</button>
      {open && (
        <div ref={innerBorderRef}>
           <SomeChild/>
        </div>
      )}
    </div>
  );

};

デモへのリンク

@ pau1fitzgeraldの回答に部分的に触発されました。

0
Ben Carp

私はあらゆる場合に解決策を作りました。

高次コンポーネントを使用して、その外側でクリックを待機したいコンポーネントをラップする必要があります。

このコンポーネントの例には、関数を受け取る "onClickedOutside"というプロップが1つだけあります。

ClickedOutside.js
import React, { Component } from "react";

export default class ClickedOutside extends Component {
  componentDidMount() {
    document.addEventListener("mousedown", this.handleClickOutside);
  }

  componentWillUnmount() {
    document.removeEventListener("mousedown", this.handleClickOutside);
  }

  handleClickOutside = event => {
    // IF exists the Ref of the wrapped component AND his dom children doesnt have the clicked component 
    if (this.wrapperRef && !this.wrapperRef.contains(event.target)) {
      // A props callback for the ClikedClickedOutside
      this.props.onClickedOutside();
    }
  };

  render() {
    // In this piece of code I'm trying to get to the first not functional component
    // Because it wouldn't work if use a functional component (like <Fade/> from react-reveal)
    let firstNotFunctionalComponent = this.props.children;
    while (typeof firstNotFunctionalComponent.type === "function") {
      firstNotFunctionalComponent = firstNotFunctionalComponent.props.children;
    }

    // Here I'm cloning the element because I have to pass a new prop, the "reference" 
    const children = React.cloneElement(firstNotFunctionalComponent, {
      ref: node => {
        this.wrapperRef = node;
      },
      // Keeping all the old props with the new element
      ...firstNotFunctionalComponent.props
    });

    return <React.Fragment>{children}</React.Fragment>;
  }
}
0
Higor Lorenzon

onClickハンドラーをトップレベルコンテナーに追加し、ユーザーがクリックするたびに状態値をインクリメントします。その値を関連するコンポーネントに渡し、値が変更されるたびに作業を行うことができます。

この場合、clickCount値が変更されるたびにthis.closeDropdown()を呼び出します。

incrementClickCountメソッドは.appコンテナ内で起動しますが、.dropdownではなく、event.stopPropagation()を使用してイベントのバブリングを防ぎます。

コードは次のようになります。

class App extends Component {
    constructor(props) {
        super(props);
        this.state = {
            clickCount: 0
        };
    }
    incrementClickCount = () => {
        this.setState({
            clickCount: this.state.clickCount + 1
        });
    }
    render() {
        return (
            <div className="app" onClick={this.incrementClickCount}>
                <Dropdown clickCount={this.state.clickCount}/>
            </div>
        );
    }
}
class Dropdown extends Component {
    constructor(props) {
        super(props);
        this.state = {
            open: false
        };
    }
    componentDidUpdate(prevProps) {
        if (this.props.clickCount !== prevProps.clickCount) {
            this.closeDropdown();
        }
    }
    toggleDropdown = event => {
        event.stopPropagation();
        return (this.state.open) ? this.closeDropdown() : this.openDropdown();
    }
    render() {
        return (
            <div className="dropdown" onClick={this.toggleDropdown}>
                ...
            </div>
        );
    }
}
0
wdm
import ReactDOM from 'react-dom' ;

class SomeComponent {

  constructor(props) {
    // First, add this to your constructor
    this.handleClickOutside = this.handleClickOutside.bind(this);
  }

  componentWillMount() {
    document.addEventListener('mousedown', this.handleClickOutside, false); 
  }

  // Unbind event on unmount to prevent leaks
  componentWillUnmount() {
    window.removeEventListener('mousedown', this.handleClickOutside, false);
  }

  handleClickOutside(event) {
    if(!ReactDOM.findDOMNode(this).contains(event.path[0])){
       console.log("OUTSIDE");
    }
  }
}
0
dipole_moment