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

Support Passive Event Listeners #6436

Open
sebmarkbage opened this Issue Apr 7, 2016 · 44 comments

Comments

Projects
None yet
@sebmarkbage
Member

sebmarkbage commented Apr 7, 2016

https://github.com/WICG/EventListenerOptions/blob/gh-pages/explainer.md

It would be good to have everything be passive by default and only opt-in to active when needed. E.g. you could listen to text input events but only preventDefault or used controlled behavior when you have active listeners.

Similarly, we could unify this with React Native's threading model. E.g. one thing we could do there is synchronously block the UI thread when there are active listeners such as handling keystrokes.

cc @vjeux @ide

@jhgg

This comment has been minimized.

Show comment
Hide comment
@jhgg

jhgg May 29, 2016

This landed in Chrome 51. Is there any updated plan to support this in React? :O

jhgg commented May 29, 2016

This landed in Chrome 51. Is there any updated plan to support this in React? :O

@aleksandar-b

This comment has been minimized.

Show comment
Hide comment
@aleksandar-b

aleksandar-b Jul 29, 2016

How is this possible if React has only one event listener on document, and then delegates to others?
@sebmarkbage

aleksandar-b commented Jul 29, 2016

How is this possible if React has only one event listener on document, and then delegates to others?
@sebmarkbage

@followdarko

This comment has been minimized.

Show comment
Hide comment
@followdarko

followdarko Aug 17, 2016

What's the current status of issue with Passive Events ?

followdarko commented Aug 17, 2016

What's the current status of issue with Passive Events ?

@radubrehar

This comment has been minimized.

Show comment
Hide comment
@radubrehar

radubrehar Aug 18, 2016

I just hit a warning in chrome about handling the wheel event, which could be optimized if it were registered as a passive event handler. So having this in React would be neat!

radubrehar commented Aug 18, 2016

I just hit a warning in chrome about handling the wheel event, which could be optimized if it were registered as a passive event handler. So having this in React would be neat!

@nolanlawson

This comment has been minimized.

Show comment
Hide comment
@nolanlawson

nolanlawson Sep 29, 2016

Contributor

You'll also want to handle arbitrary options, such as once which has already landed in Firefox nightly: https://twitter.com/mozhacks/status/758763803991474176. Full list: https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener

Contributor

nolanlawson commented Sep 29, 2016

You'll also want to handle arbitrary options, such as once which has already landed in Firefox nightly: https://twitter.com/mozhacks/status/758763803991474176. Full list: https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener

@sebmarkbage

This comment has been minimized.

Show comment
Hide comment
@sebmarkbage

sebmarkbage Sep 29, 2016

Member

FWIW, Facebook listens to active wheel events to block outer scrolling when sidebars or chat windows are scrolled. We can't implement the UI without it. We still want to support this as an option but the problem space is still incomplete so there might evolve alternative solutions to this problem that doesn't involve passive event listeners. So it is still an active design space.

Member

sebmarkbage commented Sep 29, 2016

FWIW, Facebook listens to active wheel events to block outer scrolling when sidebars or chat windows are scrolled. We can't implement the UI without it. We still want to support this as an option but the problem space is still incomplete so there might evolve alternative solutions to this problem that doesn't involve passive event listeners. So it is still an active design space.

@romulof

This comment has been minimized.

Show comment
Hide comment
@romulof

romulof Sep 29, 2016

It's important to keep both active listeners and add support passive ones.
On desktop applications you don't see any difference, but on mobile apps passive scroll listeners give a great speed boost.

Little suggestion:

<SomeElement
  onScroll={this.onScrollThatCallsPreventDefault}
  onScrollPassive={this.onScrollThatJustListens}
  ...this.props
/>

romulof commented Sep 29, 2016

It's important to keep both active listeners and add support passive ones.
On desktop applications you don't see any difference, but on mobile apps passive scroll listeners give a great speed boost.

Little suggestion:

<SomeElement
  onScroll={this.onScrollThatCallsPreventDefault}
  onScrollPassive={this.onScrollThatJustListens}
  ...this.props
/>
@radubrehar

This comment has been minimized.

Show comment
Hide comment
@radubrehar

radubrehar Sep 30, 2016

@romulof yeah, this is how you register events on the capture phase as well

<SomeElement
  onClick={this.onClick}
  onClickCapture={this.onClickCapture}
  onScrollPassive={this.onScrollPassive}
/>

so I imagine this would be the proper API to support passive events as well.

Side note: a tricky question is - how would you register passive events for the capture phase? I suppose this is not possible, by the nature of passive events. Since they are even not allowed to call event.preventDefault(), so probably this is a non-issue.

radubrehar commented Sep 30, 2016

@romulof yeah, this is how you register events on the capture phase as well

<SomeElement
  onClick={this.onClick}
  onClickCapture={this.onClickCapture}
  onScrollPassive={this.onScrollPassive}
/>

so I imagine this would be the proper API to support passive events as well.

Side note: a tricky question is - how would you register passive events for the capture phase? I suppose this is not possible, by the nature of passive events. Since they are even not allowed to call event.preventDefault(), so probably this is a non-issue.

@romulof

This comment has been minimized.

Show comment
Hide comment
@romulof

romulof Sep 30, 2016

@radubrehar, onScrollCapturePassive looks like the whole bible in camel-case.

romulof commented Sep 30, 2016

@radubrehar, onScrollCapturePassive looks like the whole bible in camel-case.

@radubrehar

This comment has been minimized.

Show comment
Hide comment
@radubrehar

radubrehar Sep 30, 2016

:) It's not the case, since there are no passive events on the capture phase.

radubrehar commented Sep 30, 2016

:) It's not the case, since there are no passive events on the capture phase.

@romulof

This comment has been minimized.

Show comment
Hide comment
@romulof

romulof Sep 30, 2016

Sure it doesn't make sense, but I would't count on it. There's also other types event binding, such as once.

Another suggestion:

<SomeElement
  onScroll={this.onScrollThatCallsPreventDefault}
/>
<SomePassiveElement
  onScroll={{
    passive: true,
    capture: true,
    handler: this.onScrollThatJustListens,
  }}
/>

This way React would have to detect whether the event handler is a function (normal binding), or and object containing binding options and the handler function.

romulof commented Sep 30, 2016

Sure it doesn't make sense, but I would't count on it. There's also other types event binding, such as once.

Another suggestion:

<SomeElement
  onScroll={this.onScrollThatCallsPreventDefault}
/>
<SomePassiveElement
  onScroll={{
    passive: true,
    capture: true,
    handler: this.onScrollThatJustListens,
  }}
/>

This way React would have to detect whether the event handler is a function (normal binding), or and object containing binding options and the handler function.

@lencioni

This comment has been minimized.

Show comment
Hide comment
@lencioni

lencioni Oct 12, 2016

I think the object approach with options makes more sense than onFooPassive, since there are other options that might be needed. If combined with @sebmarkbage's suggestion that events should be passive by default, this probably wouldn't be too cumbersome.

Another approach that comes to mind would be to attach properties to the event handler to allow them to opt out of passive mode (or toggle other options). Something like this:

class Foo extends React.Component {
  constructor() {
    this.handleScroll = this.handleScroll.bind(this);
    this.handleScroll.passive = false;
  }

  handleScroll() {
    ...
  }

  render() {
    return <div onScroll={this.handleScroll} />;
  }
}

In theory, this would work pretty nicely with decorators, once they land.

lencioni commented Oct 12, 2016

I think the object approach with options makes more sense than onFooPassive, since there are other options that might be needed. If combined with @sebmarkbage's suggestion that events should be passive by default, this probably wouldn't be too cumbersome.

Another approach that comes to mind would be to attach properties to the event handler to allow them to opt out of passive mode (or toggle other options). Something like this:

class Foo extends React.Component {
  constructor() {
    this.handleScroll = this.handleScroll.bind(this);
    this.handleScroll.passive = false;
  }

  handleScroll() {
    ...
  }

  render() {
    return <div onScroll={this.handleScroll} />;
  }
}

In theory, this would work pretty nicely with decorators, once they land.

@lencioni

This comment has been minimized.

Show comment
Hide comment
@lencioni

lencioni Oct 17, 2016

Thinking about this a little more, I think it would be better to add an event options property to the function, instead of individual options. That would allow React to only have to worry about one property instead of potentially many. So, to adjust my example above:

class Foo extends React.Component {
  constructor() {
    this.handleScroll = this.handleScroll.bind(this);
    this.handleScroll.options = { passive: false };
  }

  handleScroll() {
    ...
  }

  render() {
    return <div onScroll={this.handleScroll} />;
  }
}

Another thought that occurred to me is what might this look like if we modified JSX syntax in a way that allowed for these options to be passed in via the JSX. Here's a random example that I haven't put much thought into:

return <div onScroll={this.handleScroll, { passive: false }} />;

I've also been thinking about whether events should be passive by default or not, and I'm a bit on the fence. On one hand, this would certainly be nice for events like scroll handlers, but I worry that it would cause too much turbulence and unexpected behavior for many click handlers. We could make it so some events are passive by default and others are not, but that would probably just end up being confusing for folks, so probably not a good idea.

lencioni commented Oct 17, 2016

Thinking about this a little more, I think it would be better to add an event options property to the function, instead of individual options. That would allow React to only have to worry about one property instead of potentially many. So, to adjust my example above:

class Foo extends React.Component {
  constructor() {
    this.handleScroll = this.handleScroll.bind(this);
    this.handleScroll.options = { passive: false };
  }

  handleScroll() {
    ...
  }

  render() {
    return <div onScroll={this.handleScroll} />;
  }
}

Another thought that occurred to me is what might this look like if we modified JSX syntax in a way that allowed for these options to be passed in via the JSX. Here's a random example that I haven't put much thought into:

return <div onScroll={this.handleScroll, { passive: false }} />;

I've also been thinking about whether events should be passive by default or not, and I'm a bit on the fence. On one hand, this would certainly be nice for events like scroll handlers, but I worry that it would cause too much turbulence and unexpected behavior for many click handlers. We could make it so some events are passive by default and others are not, but that would probably just end up being confusing for folks, so probably not a good idea.

@romulof

This comment has been minimized.

Show comment
Hide comment
@romulof

romulof Oct 17, 2016

This way is pretty similar to what I proposed earlier, without modifying JSX syntax.

return <div onScroll={{ handler: this.handleScroll, passive: true }} />;

And documentation would be straightforward:

div.propTypes = {
  ...
  onScroll: React.PropTypes.oneOf([
    React.PropTypes.func,
    React.PropTypes.shape({
      handler: React.PropTypes.func.isRequired,
      capture: React.PropTypes.bool,
      passive: React.PropTypes.bool,
      once: React.PropTypes.bool,
    }),
};

romulof commented Oct 17, 2016

This way is pretty similar to what I proposed earlier, without modifying JSX syntax.

return <div onScroll={{ handler: this.handleScroll, passive: true }} />;

And documentation would be straightforward:

div.propTypes = {
  ...
  onScroll: React.PropTypes.oneOf([
    React.PropTypes.func,
    React.PropTypes.shape({
      handler: React.PropTypes.func.isRequired,
      capture: React.PropTypes.bool,
      passive: React.PropTypes.bool,
      once: React.PropTypes.bool,
    }),
};
@joshjg

This comment has been minimized.

Show comment
Hide comment
@joshjg

joshjg Oct 22, 2016

Are react events passive by default? It seems to be that way for touch events, at least. I am not able to preventDefault unless I fall back to vanilla document-level event listeners.

joshjg commented Oct 22, 2016

Are react events passive by default? It seems to be that way for touch events, at least. I am not able to preventDefault unless I fall back to vanilla document-level event listeners.

@benwiley4000

This comment has been minimized.

Show comment
Hide comment
@benwiley4000

benwiley4000 Oct 27, 2016

@joshjg React handlers are passed "synthetic events," which are sort of like native events, but different. By the way, someone with more knowledge should correct what I'm about to say because I haven't actually read the code that does this.

I'm not super familiar with the implementation details, but I know that preventDefault works at least as long as the handlers you're preventing are also React event handlers. That's been my experience, anyway.

With stopPropagation you're more likely to be out of luck (e.g. you have a document click listener which can't be bound with React, and you want to avoid bubbling up if you click inside a certain element). In that case you can use:

function stopPropagation (e) {
  e.stopPropagation();
  e.nativeEvent.stopImmediatePropagation();
}

[MDN]

This got slightly off the main topic, but the short answer is that React doesn't use passive events, they're just sometimes handled in a strange order.

benwiley4000 commented Oct 27, 2016

@joshjg React handlers are passed "synthetic events," which are sort of like native events, but different. By the way, someone with more knowledge should correct what I'm about to say because I haven't actually read the code that does this.

I'm not super familiar with the implementation details, but I know that preventDefault works at least as long as the handlers you're preventing are also React event handlers. That's been my experience, anyway.

With stopPropagation you're more likely to be out of luck (e.g. you have a document click listener which can't be bound with React, and you want to avoid bubbling up if you click inside a certain element). In that case you can use:

function stopPropagation (e) {
  e.stopPropagation();
  e.nativeEvent.stopImmediatePropagation();
}

[MDN]

This got slightly off the main topic, but the short answer is that React doesn't use passive events, they're just sometimes handled in a strange order.

@radubrehar

This comment has been minimized.

Show comment
Hide comment
@radubrehar

radubrehar Mar 20, 2017

@joshjg @benwiley4000 @gaearon Recently the chrome team has changed their approach to document-level touch events, making them passive by default. And since React attaches events at document-level, you get this new behaviour.

See https://www.chromestatus.com/features/5093566007214080

This has indirectly changed they way React behaves - I suppose React does not explicitly mention passive: false when attaching events - hence the change in behavior.

I just hit this as well - so you need to register touch events by hand, with addEventListener

radubrehar commented Mar 20, 2017

@joshjg @benwiley4000 @gaearon Recently the chrome team has changed their approach to document-level touch events, making them passive by default. And since React attaches events at document-level, you get this new behaviour.

See https://www.chromestatus.com/features/5093566007214080

This has indirectly changed they way React behaves - I suppose React does not explicitly mention passive: false when attaching events - hence the change in behavior.

I just hit this as well - so you need to register touch events by hand, with addEventListener

@chicoxyzzy

This comment has been minimized.

Show comment
Hide comment
@nolanlawson

This comment has been minimized.

Show comment
Hide comment
@nolanlawson

nolanlawson Jun 29, 2017

Contributor

Note that the Chrome passive-by-default intervention only applies to touchstart and touchmove, not wheel. So a wheel event without an explicit {passive: true} will still force synchronous scrolling, for mousewheel and two-finger trackpad scrolling. (I wrote a blog post about some of the subtleties here.)

Also we (the Edge team) have no intent to implement the same intervention, so when we ship passive event listeners you'll still want to explicitly specify {passive: true}.

Contributor

nolanlawson commented Jun 29, 2017

Note that the Chrome passive-by-default intervention only applies to touchstart and touchmove, not wheel. So a wheel event without an explicit {passive: true} will still force synchronous scrolling, for mousewheel and two-finger trackpad scrolling. (I wrote a blog post about some of the subtleties here.)

Also we (the Edge team) have no intent to implement the same intervention, so when we ship passive event listeners you'll still want to explicitly specify {passive: true}.

@mikeaustin

This comment has been minimized.

Show comment
Hide comment
@mikeaustin

mikeaustin Aug 25, 2017

FYI, I started going down the passive: false path to prevent body from scrolling on mobile when there is a scrolling div, but it's a little heavy to use preventDefault() to to block scrolling. I could add and remove the handler depending if the div is present, or fall back to a body.height = 100% approach. The body.height fix feels a little hacky, but then I wouldn't need passive: false at all.

mikeaustin commented Aug 25, 2017

FYI, I started going down the passive: false path to prevent body from scrolling on mobile when there is a scrolling div, but it's a little heavy to use preventDefault() to to block scrolling. I could add and remove the handler depending if the div is present, or fall back to a body.height = 100% approach. The body.height fix feels a little hacky, but then I wouldn't need passive: false at all.

@piotr-cz

This comment has been minimized.

Show comment
Hide comment
@piotr-cz

piotr-cz Oct 5, 2017

My use case it that I'd like to use event.preventDefault() method to prevent container scrolling when user is dragging element inside of it.

For that, I need to register event listener as non-passive (passive: false).
As browsers are switching to passive: true by default, I'd like to be able to do opposite

Unfortunately I'm not able to use touch-action: none; style because it's being applied after touch has started and probably that's why it doesn't have any effect.

piotr-cz commented Oct 5, 2017

My use case it that I'd like to use event.preventDefault() method to prevent container scrolling when user is dragging element inside of it.

For that, I need to register event listener as non-passive (passive: false).
As browsers are switching to passive: true by default, I'd like to be able to do opposite

Unfortunately I'm not able to use touch-action: none; style because it's being applied after touch has started and probably that's why it doesn't have any effect.

@KeitIG

This comment has been minimized.

Show comment
Hide comment
@KeitIG

KeitIG Oct 5, 2017

It is indeed going to be a problem really soon, I'm surprised that in two years no solution was found. And creating event listeners manually is an anti-pattern in React.

And if it creates breaking changes, then so be it. I may be missing a part of the story though.

KeitIG commented Oct 5, 2017

It is indeed going to be a problem really soon, I'm surprised that in two years no solution was found. And creating event listeners manually is an anti-pattern in React.

And if it creates breaking changes, then so be it. I may be missing a part of the story though.

@piotr-cz

This comment has been minimized.

Show comment
Hide comment
@piotr-cz

piotr-cz Oct 5, 2017

I like new event listener signature proposed by @romulof in #6436 (comment).
Besides fixing issue described here it would be possible to specify other EventListenerOptions such as once

piotr-cz commented Oct 5, 2017

I like new event listener signature proposed by @romulof in #6436 (comment).
Besides fixing issue described here it would be possible to specify other EventListenerOptions such as once

@bobvanderlinden

This comment has been minimized.

Show comment
Hide comment
@bobvanderlinden

bobvanderlinden Oct 8, 2017

I just ran into this issue. I have a canvas where the user can draw. When drawing on Android it will sometimes 'pull to refresh' instead of doing a paint-stroke. This shows it is a real-world problem. I'll be avoiding onTouch{Start,Move,End} for now and manually use addEventListener as a workaround.

I very much like the approach @romulof suggested. It seems that solution also doesn't require breaking changes.

bobvanderlinden commented Oct 8, 2017

I just ran into this issue. I have a canvas where the user can draw. When drawing on Android it will sometimes 'pull to refresh' instead of doing a paint-stroke. This shows it is a real-world problem. I'll be avoiding onTouch{Start,Move,End} for now and manually use addEventListener as a workaround.

I very much like the approach @romulof suggested. It seems that solution also doesn't require breaking changes.

@MilllerTime

This comment has been minimized.

Show comment
Hide comment
@MilllerTime

MilllerTime Oct 19, 2017

@bobvanderlinden Adding touch-action: none; to your canvas style should work for you, it really shines for that use case. The other possible values can also be quite convenient.

However as @piotr-cz pointed out, touch-action doesn't universally solve this passive events issue as a whole. I'm also running into the same issue of preventing a container from scrolling while dragging a child element. All workarounds are pretty hacky and add technical debt.

MilllerTime commented Oct 19, 2017

@bobvanderlinden Adding touch-action: none; to your canvas style should work for you, it really shines for that use case. The other possible values can also be quite convenient.

However as @piotr-cz pointed out, touch-action doesn't universally solve this passive events issue as a whole. I'm also running into the same issue of preventing a container from scrolling while dragging a child element. All workarounds are pretty hacky and add technical debt.

@roippi

This comment has been minimized.

Show comment
Hide comment
@roippi

roippi Nov 9, 2017

Unfortunately the presence of any non-passive listener can cause significant jank, even if there are no userland handlers actually hooked up to it. Chrome will tell you about it at verbose log levels: [Violation] Handling of 'wheel' input event was delayed for 194 ms due to main thread being busy. Consider marking event handler as 'passive' to make the page more responsive. (this is the top-level handler added by

export function dispatchEvent(topLevelType, nativeEvent) {
)

roippi commented Nov 9, 2017

Unfortunately the presence of any non-passive listener can cause significant jank, even if there are no userland handlers actually hooked up to it. Chrome will tell you about it at verbose log levels: [Violation] Handling of 'wheel' input event was delayed for 194 ms due to main thread being busy. Consider marking event handler as 'passive' to make the page more responsive. (this is the top-level handler added by

export function dispatchEvent(topLevelType, nativeEvent) {
)

@el-moalo-loco

This comment has been minimized.

Show comment
Hide comment
@el-moalo-loco

el-moalo-loco Dec 6, 2017

@romulof @lencioni @radubrehar Are you aware of the fact that the passive flag is not meant for usage on scroll event listeners? It should be used on events like touchmove etc. to not interfere with the browser's scrolling performance. Your examples are highly confusing to me.

Setting passive isn't important for the basic scroll event, as it cannot be canceled, so its listener can't block page rendering anyway.

Source: https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#Improving_scrolling_performance_with_passive_listeners

Additional info: https://github.com/WICG/EventListenerOptions/blob/gh-pages/explainer.md

el-moalo-loco commented Dec 6, 2017

@romulof @lencioni @radubrehar Are you aware of the fact that the passive flag is not meant for usage on scroll event listeners? It should be used on events like touchmove etc. to not interfere with the browser's scrolling performance. Your examples are highly confusing to me.

Setting passive isn't important for the basic scroll event, as it cannot be canceled, so its listener can't block page rendering anyway.

Source: https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#Improving_scrolling_performance_with_passive_listeners

Additional info: https://github.com/WICG/EventListenerOptions/blob/gh-pages/explainer.md

@lencioni

This comment has been minimized.

Show comment
Hide comment
@lencioni

lencioni Dec 6, 2017

lencioni commented Dec 6, 2017

@romulof

This comment has been minimized.

Show comment
Hide comment
@romulof

romulof Dec 7, 2017

@el-moalo-loco, I'm pretty sure that I read some documentation on Google Developers site about using passive listeners for scroll events to improve performance. I must have misread or something changed along the way. Anyway, thanks a lot for the clarification!

romulof commented Dec 7, 2017

@el-moalo-loco, I'm pretty sure that I read some documentation on Google Developers site about using passive listeners for scroll events to improve performance. I must have misread or something changed along the way. Anyway, thanks a lot for the clarification!

@philraj

This comment has been minimized.

Show comment
Hide comment
@philraj

philraj Jan 18, 2018

Contributor

@romulof @lencioni @el-moalo-loco https://developers.google.com/web/tools/lighthouse/audits/passive-event-listeners
Wheel listeners should still be passive even if scroll listeners don't have to be. I think that may be the source of confusion.

Contributor

philraj commented Jan 18, 2018

@romulof @lencioni @el-moalo-loco https://developers.google.com/web/tools/lighthouse/audits/passive-event-listeners
Wheel listeners should still be passive even if scroll listeners don't have to be. I think that may be the source of confusion.

@el-moalo-loco

This comment has been minimized.

Show comment
Hide comment
@el-moalo-loco

el-moalo-loco Jan 19, 2018

@sebmarkbage What do you think? Will this passive event listener support make it's way into React sometime?

el-moalo-loco commented Jan 19, 2018

@sebmarkbage What do you think? Will this passive event listener support make it's way into React sometime?

@googol7

This comment has been minimized.

Show comment
Hide comment
@googol7

googol7 Feb 28, 2018

Hi,

I just had to add an active event listener in componentDidMount like this:

global.addEventListener("touchstart", this.touchStart(), { passive: false })

so that I can call e.preventDefault() to stop the default scrolling of Chrome and move an element in touchStart().

In order to know which element I have to move I had to add onTouchStart to the JSX element like this:

onTouchStart={this.touchStartSetElement(element)}

In touchStartSetElement() I set a state property element that I can read in touchStart()

If React would support active event listeners this would boil down to one line.

Thanks,

Philipp

P.S.: If you try to call e.preventDefault() in a passive event listener you get this error in Chrome 56:

[Intervention] Unable to preventDefault inside passive event listener due to target being treated as passive. See https://www.chromestatus.com/features/5093566007214080

Passive events have become default with Chrome 56 by an "intervention" of Google and breaking the web somehow - but also making scrolling faster.

googol7 commented Feb 28, 2018

Hi,

I just had to add an active event listener in componentDidMount like this:

global.addEventListener("touchstart", this.touchStart(), { passive: false })

so that I can call e.preventDefault() to stop the default scrolling of Chrome and move an element in touchStart().

In order to know which element I have to move I had to add onTouchStart to the JSX element like this:

onTouchStart={this.touchStartSetElement(element)}

In touchStartSetElement() I set a state property element that I can read in touchStart()

If React would support active event listeners this would boil down to one line.

Thanks,

Philipp

P.S.: If you try to call e.preventDefault() in a passive event listener you get this error in Chrome 56:

[Intervention] Unable to preventDefault inside passive event listener due to target being treated as passive. See https://www.chromestatus.com/features/5093566007214080

Passive events have become default with Chrome 56 by an "intervention" of Google and breaking the web somehow - but also making scrolling faster.

@steipete

This comment has been minimized.

Show comment
Hide comment
@steipete

steipete Mar 8, 2018

This is becoming more of an issue since Safari as of iOS 11.3 also defaults to passive, and the classical workaround of touch-action:none is not supported there.

steipete commented Mar 8, 2018

This is becoming more of an issue since Safari as of iOS 11.3 also defaults to passive, and the classical workaround of touch-action:none is not supported there.

@dantman

This comment has been minimized.

Show comment
Hide comment
@dantman

dantman Mar 15, 2018

Contributor

I've proposed an RFC reactjs/rfcs#28 that would allow creating custom ref handlers that work like props (i.e. you use properties like you would onClick but instead use computed property syntax and the handler gets ref and prop value info and updates). These can be used to create libraries for just about any advanced use case you have.

  • Passive, explicitly non-passive, once, and capturing events are all easy to do with it.
  • Things even more advanced than just event handler registration can be done with them.
  • From the user's perspective they're simple to use, just pass what the library gives you as a computed property to any element. e.g. import {onScroll} from 'react-passive-events'; <div [onScroll]={scrollHandler} />

I do not think these should be the way to register all events.

However, instead of coming up with complex ways to handle registering all possible types of events (capturing, passive, etc...) I recommend deciding what the default behaviour should be for most events (passive or non-passive) and using these registered props to handle more advanced use cases.

Contributor

dantman commented Mar 15, 2018

I've proposed an RFC reactjs/rfcs#28 that would allow creating custom ref handlers that work like props (i.e. you use properties like you would onClick but instead use computed property syntax and the handler gets ref and prop value info and updates). These can be used to create libraries for just about any advanced use case you have.

  • Passive, explicitly non-passive, once, and capturing events are all easy to do with it.
  • Things even more advanced than just event handler registration can be done with them.
  • From the user's perspective they're simple to use, just pass what the library gives you as a computed property to any element. e.g. import {onScroll} from 'react-passive-events'; <div [onScroll]={scrollHandler} />

I do not think these should be the way to register all events.

However, instead of coming up with complex ways to handle registering all possible types of events (capturing, passive, etc...) I recommend deciding what the default behaviour should be for most events (passive or non-passive) and using these registered props to handle more advanced use cases.

@alexreardon

This comment has been minimized.

Show comment
Hide comment
@alexreardon

alexreardon Mar 31, 2018

All touch events are now passive by default in iOS 11.3. Thus calling event.preventDefault() in any touch event handler is now non-effective 😢

https://codesandbox.io/s/l4kpy569ol

alexreardon commented Mar 31, 2018

All touch events are now passive by default in iOS 11.3. Thus calling event.preventDefault() in any touch event handler is now non-effective 😢

https://codesandbox.io/s/l4kpy569ol

@alexreardon

This comment has been minimized.

Show comment
Hide comment
@alexreardon

alexreardon Mar 31, 2018

Without being able to force non-passive event handlers we are having a hard time working around the iOS 11.3 changes atlassian/react-beautiful-dnd#413

alexreardon commented Mar 31, 2018

Without being able to force non-passive event handlers we are having a hard time working around the iOS 11.3 changes atlassian/react-beautiful-dnd#413

@KeitIG

This comment has been minimized.

Show comment
Hide comment
@KeitIG

KeitIG Apr 11, 2018

I came to see how Vue.js was handling this, and I quite like their approach:

<!-- the click event's propagation will be stopped -->
<a v-on:click.stop="doThis"></a>

<!-- the submit event will no longer reload the page -->
<form v-on:submit.prevent="onSubmit"></form>

<!-- modifiers can be chained -->
<a v-on:click.stop.prevent="doThat"></a>

<!-- just the modifier -->
<form v-on:submit.prevent></form>

There is this list of modifiers:

  • .stop
  • .prevent
  • .capture
  • .self
  • .once
  • .passive

And you can compose events the way you like. Maybe this can help as inspiration for this issue.

KeitIG commented Apr 11, 2018

I came to see how Vue.js was handling this, and I quite like their approach:

<!-- the click event's propagation will be stopped -->
<a v-on:click.stop="doThis"></a>

<!-- the submit event will no longer reload the page -->
<form v-on:submit.prevent="onSubmit"></form>

<!-- modifiers can be chained -->
<a v-on:click.stop.prevent="doThat"></a>

<!-- just the modifier -->
<form v-on:submit.prevent></form>

There is this list of modifiers:

  • .stop
  • .prevent
  • .capture
  • .self
  • .once
  • .passive

And you can compose events the way you like. Maybe this can help as inspiration for this issue.

@stalniy

This comment has been minimized.

Show comment
Hide comment
@stalniy

stalniy Jul 16, 2018

@KeitIG I’m working on a fork of Vue-loader which is designed for React

https://github.com/stalniy/react-webpack-loader

stalniy commented Jul 16, 2018

@KeitIG I’m working on a fork of Vue-loader which is designed for React

https://github.com/stalniy/react-webpack-loader

@Robinfr

This comment has been minimized.

Show comment
Hide comment
@Robinfr

Robinfr Jul 18, 2018

This makes me sad

selection_028

Robinfr commented Jul 18, 2018

This makes me sad

selection_028

@phaistonian

This comment has been minimized.

Show comment
Hide comment
@phaistonian

phaistonian Aug 12, 2018

Not sure if this is suggested already - or if it makes any sense to anyone other but me, however:

onTouchStart={listener} 

to

onTouchStart={listener, options}

would both make passing options such as { passive, true, once: true } a natural way to do it, and it would also match addEventListener schema.

phaistonian commented Aug 12, 2018

Not sure if this is suggested already - or if it makes any sense to anyone other but me, however:

onTouchStart={listener} 

to

onTouchStart={listener, options}

would both make passing options such as { passive, true, once: true } a natural way to do it, and it would also match addEventListener schema.

@alexreardon

This comment has been minimized.

Show comment
Hide comment
@alexreardon

alexreardon Aug 12, 2018

Moving towards @phaistonian's suggestion would remove the need for any of the onEventNameCapture handlers that exist today

alexreardon commented Aug 12, 2018

Moving towards @phaistonian's suggestion would remove the need for any of the onEventNameCapture handlers that exist today

@radubrehar

This comment has been minimized.

Show comment
Hide comment
@radubrehar

radubrehar Aug 13, 2018

@alexreardon I really think that's not an option, since in plain js, listener, options is an expression, that evaluates to options, so the construction above is not what you mean it to be. This would require changing the way jsx compiles to js and would be a breaking change. I doubt the react team would go this route.

Opinions?

radubrehar commented Aug 13, 2018

@alexreardon I really think that's not an option, since in plain js, listener, options is an expression, that evaluates to options, so the construction above is not what you mean it to be. This would require changing the way jsx compiles to js and would be a breaking change. I doubt the react team would go this route.

Opinions?

@alexreardon

This comment has been minimized.

Show comment
Hide comment
@alexreardon

alexreardon Aug 13, 2018

Regarding it being an expression, it could be done differently with the same intention.

There are probably a lot of options. Options include:

import {handler} from 'React';

onTouchStart={handler(listener, options)}
onTouchStart={{listener, options}}

alexreardon commented Aug 13, 2018

Regarding it being an expression, it could be done differently with the same intention.

There are probably a lot of options. Options include:

import {handler} from 'React';

onTouchStart={handler(listener, options)}
onTouchStart={{listener, options}}
@philraj

This comment has been minimized.

Show comment
Hide comment
@philraj

philraj Aug 13, 2018

Contributor

or

onTouchStart={[listener, options]}

or

onTouchStart={listener} onTouchStartOptions={options}

I like the idea of passing an object the most though. Either way, this needs a solution.

Contributor

philraj commented Aug 13, 2018

or

onTouchStart={[listener, options]}

or

onTouchStart={listener} onTouchStartOptions={options}

I like the idea of passing an object the most though. Either way, this needs a solution.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment