-
Notifications
You must be signed in to change notification settings - Fork 47.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Detecting 'outside' events using the same logic as with portals #10962
Comments
There isn't anything new specifically for this. events in portals sort of "just work" due to how they are implemented, not because there is anything new in the event code. The way it works in a nutshell is that when React sees an event at the top level (on |
Thanks for the info. I have some code that tries to do this using the existing event system. I'll leave it here in case anyone happens to find it useful, but it is a bit messy: import * as React from 'react';
export interface IDetectExternalEventsProps {
component?: string;
reactEvents: Array<string>;
domEvents: Array<string>;
onExternalEvent: (event: Event) => void;
}
export class DetectExternalEvents extends React.Component<IDetectExternalEventsProps> {
// Fields
private _disposeTopLevelEvents: (() => void) | null = null;
private _lastInterceptedEvent: Event | null = null;
// Methods (React.Component)
public render() {
const {
children,
component = 'div',
reactEvents
} = this.props;
const events = reactEvents.reduce((obj, eventName) => {
obj[eventName] = this._handleWrapperEvent;
return obj;
}, {} as { [key: string]: any });
return React.createElement(component, events, children);
}
public componentDidMount() {
this._setupTopLevelEvents();
}
public componentDidUpdate() {
this._setupTopLevelEvents();
}
public componentWillUnmount() {
if (this._disposeTopLevelEvents) {
this._disposeTopLevelEvents();
this._disposeTopLevelEvents = null;
}
}
// Methods
private _setupTopLevelEvents() {
if (this._disposeTopLevelEvents) {
this._disposeTopLevelEvents();
this._disposeTopLevelEvents = null;
}
const { domEvents } = this.props;
domEvents.forEach((eventName) => {
window.addEventListener(eventName, this._handleTopLevelEvent);
});
this._disposeTopLevelEvents = () => {
domEvents.forEach((eventName) => {
window.removeEventListener(eventName, this._handleTopLevelEvent);
});
};
}
// Event Handlers
private _handleWrapperEvent = (event: React.SyntheticEvent<Element>) => {
this._lastInterceptedEvent = event.nativeEvent;
}
private _handleTopLevelEvent = (event: Event) => {
const { onExternalEvent } = this.props;
const wasEventIntercepted = this._lastInterceptedEvent === event;
if (!wasEventIntercepted) {
onExternalEvent(event);
}
this._lastInterceptedEvent = null;
}
} Usage: <DetectExternalEvents domEvents={['click']} reactEvents={['onClick']}
onExternalEvent={() => console.log('Clicked outside')}>
<div>Any content, maybe including portals</div>
</DetectExternalEvents> Basically, if we detect the event before it reaches Unfortunately it:
|
There’s nothing new on that front, but you can follow #285 in case we add something. |
I ended up using the following solution if it helps anyone. Here is an explanation of the approach export default class ClickOutside extends React.Component {
static propTypes = {
children: PropTypes.element.isRequired,
onClickOutside: PropTypes.func.isRequired
};
isClickedInside = false;
componentDidMount() {
document.addEventListener('click', this.handleDocumentClick);
}
componentWillUnmount() {
document.removeEventListener('click', this.handleDocumentClick);
}
handleDocumentClick = () => {
if (this.isClickedInside) {
this.isClickedInside = false;
return;
}
this.props.onClickOutside();
};
handleClick = () => {
this.isClickedInside = true;
};
render() {
return React.cloneElement(
React.Children.only(this.props.children), {
onClickCapture: this.handleClick
}
);
}
} |
I was surprised (pleasantly so) to read that events bubble through portals and up the component tree rather than the DOM tree. Obviously this means I can detect clicks that occur within my component or child components without having to worry about whether they happen to be portal hosted.
However, another fairly common thing you may want to do is detect whether an event occurred outside of a component or child components. There are plenty of libraries out there that do this, but they all (AFAIK) use the DOM tree so don't have this same behaviour.
I was curious whether or not there is anything new, but unadvertised, in React 16 that can help with this part of the puzzle?
The text was updated successfully, but these errors were encountered: