Skip to content
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

Moving to React Portal after touchstart swallows future touch events #13113

Closed
alexreardon opened this issue Jun 26, 2018 · 11 comments
Closed

Moving to React Portal after touchstart swallows future touch events #13113

alexreardon opened this issue Jun 26, 2018 · 11 comments

Comments

@alexreardon
Copy link

alexreardon commented Jun 26, 2018

Do you want to request a feature or report a bug?

Bug 🐞

What is the current behavior?

When you move a component into a React Portal in response to a touchstart the touchmove and touchend events are swallowed for the rest of the interaction

If the current behavior is a bug, please provide the steps to reproduce and if possible a minimal demo of the problem. Your bug will get fixed much faster if we can run your code and it doesn't have dependencies other than React. Paste the link to your JSFiddle (https://jsfiddle.net/Luktwrdm/) or CodeSandbox (https://codesandbox.io/s/new) example below:

Example: https://codesandbox.io/s/nr75vkklnm

You will either need to be on touch device, or enable touch sensors in your browser to see this

Steps:

  1. add a onTouchStart listener to a component
  2. in response to onTouchStart move the component into a Portal
  3. touchmove events and the touchend events are then blocked for the rest of the touch interaction

If the component is already in a component before the touchstart event then the events are emitted correctly: https://codesandbox.io/s/v54x54vp5

I have also created a vanilla js example that has a portal implementation. It moves the element into a portal after touch start. It is correctly allowing touch touchmove and touchend events: https://codesandbox.io/s/r4mn0yj6po

What is the expected behavior?

That the touchmove and touchend events are published

Which versions of React, and which browser / OS are affected by this issue? Did this work in previous versions of React?

Reproduced bug in Firefox and Chrome

React version: tested on 16.1, 16.3 and 16.4.1

@gaearon
Copy link
Collaborator

gaearon commented Aug 2, 2018

Is that a bug though, or just how the DOM works? The element is technically a different one unless I'm missing something.

@philipp-spiess
Copy link
Contributor

philipp-spiess commented Aug 3, 2018

This is indeed how the DOM behaves normally. The issue here is that touch events have a locked target: When a touchstart event occurs on a given target, all further touch event have the same target (you can read up on this in the spec).

MDN has a pretty good explanation for this issue:

Note that if the target element is removed from the document, events will still be targeted at it, and hence won't necessarily bubble up to the window or document anymore. If there is any risk of an element being removed while it is being touched, the best practice is to attach the touch listeners directly to the target.

Now, as Dan pointed out, the issue is that rendering inside a portal will create a new DOM element (since the element will have a different parent, the reconciler will always create a new element). And since the original element is no longer attached to the same document, events won’t bubble there anymore. In your vanilla example, this is not the case since the element is moved instead of recreated. Check out this example that shows the same issue.

As a workaround you can probably add native event listeners to the DOM node using componentDidMount and remove them on touchend/touchcancel. Or use pointer events which do not suffer from that issue since there, the target will always be the latest intersection (like with mouse events) - make sure any eventual polyfill handles this correctly though.

@nhunzaker
Copy link
Contributor

@philipp-spiess Is there any action we can take on the React side? I'm considering closing this, but I wanted to check first.

@philipp-spiess
Copy link
Contributor

@nhunzaker Nope, I think this works as intended.

@nhunzaker
Copy link
Contributor

Okay. Thanks for giving a fantastic break down of the problem and possible solutions! I'll close this out.

@alexreardon
Copy link
Author

Thanks @philipp-spiess for the detail!! It is much appreciated. I will see if I can work around this DOM behaviour on my end

@alexreardon
Copy link
Author

You are right, my example was moving a DOM element rather than creating a new one - which is where the problem was.

Side point: codesandbox is great for sharing these things 👍

@alexreardon
Copy link
Author

Also, thanks for following up @gaearon

@alexreardon
Copy link
Author

Linking for historical purposes: #3965

@parris
Copy link

parris commented Nov 15, 2018

For those needing a solution for drag/drop and dealing with the touch target changing parents - I wrote up a potential/hacky solution here - https://gist.github.com/parris/dda613e3ae78f14eb2dc9fa0f4bfce3d in short attaching events during touchstart directly to the target seems to work for some reason.

@alexreardon
Copy link
Author

I can confirm that attaching events to the event.target still publish after the element is removed. We will be shipping this change in react-beautiful-dnd soon atlassian/react-beautiful-dnd#1225

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

No branches or pull requests

5 participants