import React from 'react';
import PropTypes from 'prop-types';

export default class ContextMenuInterceptor extends React.Component {
  static propTypes = {
    onWillShowContextMenu: PropTypes.func.isRequired,
    children: PropTypes.element.isRequired,
  }

  static registration = new Map()

  static handle(event) {
    for (const [element, callback] of ContextMenuInterceptor.registration) {
      if (element.contains(event.target)) {
        callback(event);
      }
    }
  }

  static dispose() {
    document.removeEventListener('contextmenu', contextMenuHandler, {capture: true});
  }

  componentDidMount() {
    // Helpfully, addEventListener dedupes listeners for us.
    document.addEventListener('contextmenu', contextMenuHandler, {capture: true});
    ContextMenuInterceptor.registration.set(this.element, this.props.onWillShowContextMenu);
  }

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

  componentWillUnmount() {
    ContextMenuInterceptor.registration.delete(this.element);
  }
}

function contextMenuHandler(event) {
  ContextMenuInterceptor.handle(event);
}

import React from 'react';
import PropTypes from 'prop-types';

export default class ContextMenuInterceptor extends React.Component {
  static propTypes = {
    onWillShowContextMenu: PropTypes.func.isRequired,
    children: PropTypes.element.isRequired,
  }

  static registration = new Map()

  static handle(event) {
    for (const [element, callback] of ContextMenuInterceptor.registration) {
      if (element.contains(event.target)) {
        callback(event);
      }
    }
  }

  static dispose() {
    document.removeEventListener('contextmenu', contextMenuHandler, {capture: true});
  }

  componentDidMount() {
    // Helpfully, addEventListener dedupes listeners for us.
    document.addEventListener('contextmenu', contextMenuHandler, {capture: true});
    ContextMenuInterceptor.registration.set(this.element, (...args) => this.props.onWillShowContextMenu(...args));
  }

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

  componentWillUnmount() {
    ContextMenuInterceptor.registration.delete(this.element);
  }
}

function contextMenuHandler(event) {
  ContextMenuInterceptor.handle(event);
}

Fix stale function reference when calling context menu handler
Fix stale function reference when calling context menu handler

Upon mounting, ContextMenuInterceptor retained the onWillShowContextMenu
prop as a direct function reference; when updating the component with
a new onWillShowContextMenu, the retained function was not updated. As
a result, right-clicking on the wrapped element could potentially call
a previously-passed value of onWillShowContextMenu. The solution is to
instead retain a function that has dynamic access to the latest
onWillShowContextMenu prop.
