it-swarm.com.de

Klick außerhalb der React-Komponente erkennen

Ich suche nach einem Weg, um herauszufinden, ob ein Klickereignis außerhalb einer Komponente aufgetreten ist, wie in diesem article beschrieben. jQueryestly () wird verwendet, um zu sehen, ob das Ziel eines Click-Ereignisses das Element dom als eines seiner übergeordneten Elemente hat. Wenn es eine Übereinstimmung gibt, gehört das Klickereignis zu einem der untergeordneten Elemente und wird daher nicht als außerhalb der Komponente betrachtet.

Daher möchte ich in meiner Komponente einen Click-Handler an das Fenster anhängen. Wenn der Handler ausgelöst wird, muss ich das Ziel mit den dom-Kindern meiner Komponente vergleichen.

Das Klickereignis enthält Eigenschaften wie "Pfad", der scheinbar den dom-Pfad enthält, den das Ereignis zurückgelegt hat. Ich bin nicht sicher, was ich vergleichen soll oder wie ich es am besten durchqueren kann, und ich denke, jemand muss das schon in eine clevere Nutzenfunktion gebracht haben ... Nein?

224

Die folgende Lösung verwendet ES6 und befolgt Best Practices für das Binden sowie das Festlegen des Ref durch eine Methode.

Um es in Aktion zu sehen: Code Sandbox Demo

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,
};
341
Ben Bud

Hier ist die Lösung, die für mich am besten funktioniert hat, ohne dass Ereignisse an den Container angehängt werden:

Bestimmte HTML-Elemente können sogenannte " focus " enthalten, beispielsweise Eingabeelemente. Diese Elemente reagieren auch auf das blur -Ereignis, wenn sie diesen Fokus verlieren. 

Um jedem Element die Fähigkeit zu geben, den Fokus zu haben, stellen Sie sicher, dass das tabindex-Attribut auf einen anderen Wert als -1 gesetzt ist. In normalem HTML wäre dies das Setzen des tabindex-Attributs, aber in React müssen Sie tabIndex verwenden (beachten Sie den Großbuchstaben I).

Sie können dies auch über JavaScript mit element.setAttribute('tabindex',0) tun.

Dafür habe ich es verwendet, um ein benutzerdefiniertes DropDown-Menü zu erstellen.

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

Nachdem ich hier viele Methoden ausprobiert hatte, entschied ich mich für github.com/Pomax/react-onclickoutside , da es so vollständig ist.

Ich habe das Modul über npm installiert und in meine Komponente importiert:

import onClickOutside from 'react-onclickoutside'

Dann habe ich in meiner Komponentenklasse die handleClickOutside-Methode definiert:

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

Beim Exportieren meiner Komponente habe ich sie in onClickOutside() verpackt:

export default onClickOutside(NameOfComponent)

Das ist es.

79
Beau Smith

Ich war bei dem gleichen Thema hängen geblieben. Ich bin zu spät zur Party hier, aber für mich ist das eine wirklich gute Lösung. Hoffentlich wird es jemand anderem helfen. Sie müssen findDOMNode aus react-dom importieren.

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

Ansatz "React Hooks" (16.8 +)

Sie können einen wiederverwendbaren Hook namens useComponentVisible erstellen.

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

Dann fügen Sie in der Komponente die Funktionalität hinzu, um Folgendes auszuführen:

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

}

Hier finden Sie ein codesandbox - Beispiel.

46
Paul Fitzgerald

Ich habe dank Ben Alpert auf discussion.reactjs.org eine Lösung gefunden. Der vorgeschlagene Ansatz fügt dem Dokument einen Handler hinzu, der sich jedoch als problematisch herausstellte. Wenn Sie auf eine der Komponenten in meinem Baum klicken, wurde ein Rerender erstellt, durch den das angeklickte Element beim Update entfernt wurde. Da der rerender von React before der Dokument-Body-Handler aufgerufen wird, wurde das Element nicht als "innerhalb" der Baumstruktur erkannt.

Die Lösung hierfür bestand darin, den Handler im Anwendungsstammelement hinzuzufügen. 

main:

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

komponente:

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

Keine der anderen Antworten funktionierte für mich. Ich habe versucht, ein Popup auf Unschärfe zu verbergen, aber da der Inhalt absolut positioniert war, schoss der onBlur auch auf das Klicken des inneren Inhalts.

Hier ist ein Ansatz, der für mich funktioniert hat:

// 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.
    }
},

Hoffe das hilft.

22
Niyaz

[Update] Lösung mit React ^ 16.8 mit Hooks

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;

Lösung mit 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;
11
onoya

Hier ist mein Ansatz (Demo - https://jsfiddle.net/agymay93/4/ ):

Ich habe eine spezielle Komponente mit dem Namen WatchClickOutside erstellt, und diese kann wie folgt verwendet werden (ich gehe davon aus, dass JSX-Syntax gilt):

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

Hier ist der Code der WatchClickOutside Komponente:

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

Für diejenigen, die eine absolute Positionierung benötigen, habe ich mich für eine einfache Option entschieden, eine Wrapper-Komponente hinzuzufügen, die so gestaltet ist, dass die gesamte Seite mit einem transparenten Hintergrund abgedeckt wird. Dann können Sie einen OnClick zu diesem Element hinzufügen, um Ihre innere Komponente zu schließen. 

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

Wenn Sie jetzt einen Click-Handler für Inhalt hinzufügen, wird das Ereignis auch in das obere Div übertragen und löst daher den HandlerOutsideClick aus. Wenn dies nicht Ihr gewünschtes Verhalten ist, stoppen Sie einfach die Ereignisfortführung Ihres Handlers.

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

`

2
Anthony Garant

Um die akzeptierte Antwort von Ben Bud zu erweitern, wenn Sie Stil-Komponenten verwenden, erhalten Sie beim Übergeben von Refs einen Fehler wie "this.wrapperRef.contains ist keine Funktion".

Der in den Kommentaren vorgeschlagene Fix, die stilisierte Komponente mit einem div zu umschließen und dort den Ref zu übergeben, funktioniert . Nachdem dies gesagt wurde, erklären sie in ihren docs bereits den Grund dafür und die korrekte Verwendung von Refs innerhalb von Stilkomponenten:

Durch Übergeben eines Ref Prop an eine Stilkomponente erhalten Sie eine Instanz des StyledComponent-Wrappers, nicht jedoch den darunter liegenden DOM-Knoten. Dies ist darauf zurückzuführen, wie die Refs funktionieren. Es ist nicht möglich, DOM-Methoden, wie etwa focus, direkt auf unseren Wrappern aufzurufen. Um einen Verweis auf den tatsächlichen, umschlossenen DOM-Knoten zu erhalten, übergeben Sie den Rückruf stattdessen an die Eigenschaft innerRef.

So wie:

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

Dann können Sie direkt über die Funktion "handleClickOutside" darauf zugreifen:

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

Dies gilt auch für den "onBlur" -Ansatz:

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

Ich habe dieses Modul verwendet (Ich habe keine Verbindung mit dem Autor)

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

Es hat die Arbeit gut gemacht.

2
ErichBSchulz

Lösung mit React Hooks (16.8 +)

tl; dr ( Codesandbox ):

"Innere" Komponente

function OutsideClickComp() {
  const innerRef = useRef(null);
  useOuterClickNotifier(
    // if you want to call it once only,
    // don't provide an anonymous function here
    e => alert("clicked outside of this component!"),
    innerRef
  );
  return (
    <div ref={innerRef}>
      inside component
    </div>
  );
}

Benutzerdefinierte Haken

function useOuterClickNotifier(onOuterClick, innerRef) {
  useLayoutEffect(
    () => {
      document.addEventListener("click", handleClick);
      // called for each previous layout effect
      return () => document.removeEventListener("click", handleClick);

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

Hier ist meine Lösung: Ich musste eine Art Popover-Menü implementieren, das automatisch geschlossen wird, wenn Sie außerhalb des Menü-Containers klicken. Mit Haken, IMO die Vorteile sind:

  • sideeffect/Stateful-Logik der Registrierung von externen Klicks kann vollständig abstrahiert werden und vereinfacht die "innere" Komponente
  • wiederverwendung von useOuterClickNotifier überall
  • keine zusätzliche Wrapper-Komponente (ersetzt durch Hook-Logik)

Abhängig von Ihrem Anwendungsfall könnten Sie dann etwas im äußeren Click-Callback tun, der hier aus Gründen der Vereinfachung mit alert("clicked outside of this component!") unterlegt ist. Z.B. Setzen Sie einen Status mit useState hook oder rufen Sie einen Toggle-Callback auf (in meinem Fall), um ein Popup-Menü zu öffnen/schließen.

Edit: useLayoutEffect vs useEffect

useEffect wird nach der Renderphase während eines deferred - Ereignisses ausgelöst, während useLayoutEffect nach jedem Rendering synchron aufgerufen wird. Daher verhält sich useLayoutEffect wie die Lebenszyklusmethoden componentDidMount und componentDidUpdate in Klassenkomponenten. Es wird verwendet, um sicherzustellen, dass, wenn useOuterClickNotifier Hook-Funktionseingaben geändert wurden, ein früherer Klicklistener für Dokumente nicht ausgelöst werden kann, bevor er nicht registriert wird. Siehe - Effekt-Hook-Timings und useLayoutEffect

Hoffentlich hilft das.

2
ford04
componentWillMount(){

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

handleClickOutside(event) {

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

Ereignis path[0] ist das zuletzt angeklickte Element

2
Iván

Dies hat bereits viele Antworten, aber sie behandeln e.stopPropagation() nicht und verhindern, dass Sie auf Reaktionslinks außerhalb des zu schließenden Elements klicken.

Aufgrund der Tatsache, dass React über einen eigenen künstlichen Ereignishandler verfügt, können Sie kein Dokument als Basis für Ereignis-Listener verwenden. Sie müssen e.stopPropagation(), bevor React das Dokument selbst verwendet. Wenn Sie stattdessen beispielsweise document.querySelector('body') verwenden. Sie können das Klicken über den Link React verhindern. Das folgende Beispiel zeigt, wie ich das Klicken außerhalb und Schließen verwalte.
Dies verwendet ES6 und 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;
1
Richard Herries

Ich tat dies zum Teil, indem ich this und den offiziellen Anweisungen von React zum Umgang mit Refs folgte, die eine Reaktion erfordern. Dies ist das einzige, was für mich funktioniert hat, nachdem ich hier einige der anderen Vorschläge ausprobiert habe ...

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***
)}}
1
RawCode

Ein Beispiel mit Strategie

Ich mag die angebotenen Lösungen, die dasselbe tun, indem sie einen Wrapper um die Komponente erstellen.

Da dies eher ein Verhalten ist, dachte ich an Strategie und kam zu folgendem. 

Ich bin neu bei React und brauche ein wenig Hilfe, um in den Anwendungsfällen etwas Boilerplate zu sparen 

Bitte überprüfen Sie und sagen Sie mir, was Sie denken.

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

Verwendungsbeispiel

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() {...}
}

Anmerkungen

Ich dachte daran, die übergebenen component Lebenszyklus-Hooks zu speichern und sie mit ähnlichen Methoden zu überschreiben:

const baseDidMount = component.componentDidMount;

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

component ist die Komponente, die an den Konstruktor von ClickOutsideBehavior übergeben wird.
Dadurch wird die Aktivierungs-/Deaktivierungsboilerplatte vom Benutzer dieses Verhaltens entfernt, es sieht jedoch nicht sehr schön aus

1
kidroca

Mein größtes Anliegen bei all den anderen Antworten ist, dass ich Click-Events vom Stamm/Elternteil aus filtern muss. Am einfachsten fand ich es, einfach ein Geschwisterelement mit der Position position: fix, einen Z-Index 1 hinter dem Dropdown-Menü und das Click-Ereignis für das fixierte Element in derselben Komponente zu behandeln. Hält alles zentral zu einer bestimmten Komponente.

Beispielcode

#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;
    }
}
1
metroninja

Damit die 'focus' - Lösung für das Dropdown mit Ereignislistenern funktioniert, können Sie sie mit onMouseDown event anstelle von onClick hinzufügen. Auf diese Weise wird das Ereignis ausgelöst und danach schließt sich das Popup wie folgt:

<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

Ich habe dies aus dem folgenden Artikel gefunden:

render () { return ( {this.node = node;}} > Popover umschalten __. {this.state.popupVisible && ( Ich bin ein popover! )} ); } }

Hier ist ein großartiger Artikel zu diesem Problem: "Klicks außerhalb von React-Komponenten verarbeiten"https://larsgraubner.com/handle-outside-clicks-react/

0
Amanda Koster

UseOnClickOutside Hook - React 16.8 +

Erstellen Sie eine allgemeine useOnOutsideClick-Funktion

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, []);

Verwenden Sie dann den Haken in einer beliebigen Funktionskomponente.

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

};

Link zur Demo

Teilweise inspiriert von @pau1fitzgerald Antwort.

0
Ben Carp

Ich habe eine Lösung für alle Fälle gefunden.

Verwenden Sie eine Komponente höherer Ordnung, um die Komponente, die Sie auf Klicks außerhalb abhören möchten, zu umschließen.

Dieses Komponentenbeispiel hat nur eine Eigenschaft: "onClickedOutside", die eine Funktion empfängt.

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

Fügen Sie Ihrem Container der obersten Ebene einen onClick -Handler hinzu, und erhöhen Sie einen Statuswert, wenn der Benutzer in den Bereich klickt. Übergeben Sie diesen Wert an die entsprechende Komponente und wann immer sich der Wert ändert, können Sie arbeiten.

In diesem Fall rufen wir this.closeDropdown() auf, wenn sich der clickCount-Wert ändert.

Die incrementClickCount-Methode wird innerhalb des .app-Containers ausgelöst, jedoch nicht im .dropdown, da wir event.stopPropagation() verwenden, um das Blasen von Ereignissen zu verhindern.

Ihr Code könnte am Ende so aussehen:

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