Skip to content

Responder Event System for Web #1568

Closed
Closed
@necolas

Description

@necolas

Responder Event System for Web

The Responder Event System (RES) is a gesture system that manages the lifecycle of gestures. It was designed for React Native to help support the development of native-quality gestures. A pointer may transition through several different phases while the gesture (e.g., tap, scroll, swipe) is being determined, and a pointer may be used simultaneously alongside other pointers. The Responder Event System helps developers manage pointer interactions by providing a single, global “interaction lock” on views. For a view to become the “responder” means that pointer interactions are exclusive to that view and none other. A view can negotiate becoming the “responder” without requiring knowledge about other views.

Using the Responder Event System

This system is currently provided via the ResponderEventPlugin. The plugin and related injection helpers are also published in the ReactDOM npm package as a special bundle of unstable APIs under react-dom/unstable-native-dependencies. This bundle was created for, and is used exclusively by react-native-web to enable the RES in ReactDOM.

However, this setup has problems which are described below.

Problems for React

  1. The unstable APIs in the public release of ReactDOM are part of the legacy event system. The ReactDOM event system is being refactored and simplified, but OSS libraries depending on these unstable APIs will eventually block completion of this work.
  2. The unstable APIs are not available in Facebook's internal build of ReactDOM. This blocks the experimental use of react-native-web by teams there.

Problems for React Native for Web

  1. Injecting the ResponderEventPlugin causes every host component (e.g., div) in ReactDOM to gain props for the Responder System.
  2. The internals of the unstable APIs need to be mutated by react-native-web to monkey-patch issues with the implementation when it is running on the web. Occasionally, these internals have been changed in patches to React and caused unexpected downstream regressions.
  3. The ResponderEventPlugin is difficult and risky to reliably patch: the plugin is also used by React Native; the plugin is tied to the legacy event system; the plugin unit tests are esoteric and tied to React implementation details; performing manual integration tests with react-native-web is tedious and error-prone; and there is a lack of relevant context among code reviewers.
  4. The implementation of the plugin is not well suited to the needs of web apps.
    • It doesn’t reliably support both mouse and touch interactions, as it doesn’t account for browsers producing emulated mouse events after touch events.
    • It doesn’t hand over control to the native system when a container scrolls, the window loses focus, the context menu opens, or selection changes.
    • It doesn’t always correctly set the currentTarget of a Responder Event to the current “responder“ view (see PanResponder: locationX is not relative to the element and is not deterministic #693).

Replacing the Responder Event System

The proposed solution to the above problems is to reimplement the Responder Event System in user-space, i.e., using the native DOM event system and avoiding ReactDOM’s event system altogether. The RES can then be made available to ReactDOM apps via a custom React Hook. This will be used by react-native-web to implement the Responder callbacks on View and Text without altering the rest of the ReactDOM event system by injecting the ResponderEventPlugin.

Benefits for React

  1. Remove the unstable APIs in the public release of ReactDOM. This allows us to work towards removing the event plugin system, as well as independently evolving the implementation for React Native.
  2. Developing this hook will function as practical R&D for future event-related APIs in ReactDOM. Both in terms of assessing new low-level APIs like useEvent and as a reference for alternative systems to the RES. This can be done without first requiring changes to the ReactDOM event system.
  3. Enable experimental use of react-native-web at Facebook. This allows teams to safely experiment without introducing internal dependencies on unstable APIs (whether legacy or experimental) in ReactDOM itself. It would also be easier to debug, contribute patches, or fork without requiring knowledge of the esoteric details of the ResponderEventPlugin and React’s event system.

Benefits for React Native for Web

  1. No dependency on unstable APIs and no mutating of React’s internal APIs or plugin injection by react-native-web. This should make the UX more reliable and the library less prone to regressions from upstream changes. It should also work with "React alternatives".
  2. Easier to iterate on the implementation and its integration with the host environment.
  3. No addition of RES props to the host components provided by ReactDOM when react-native-web is used.
  4. The RES can account for the idiosyncratic requirements of web apps, such as the conditions in which to terminate the current responder and hand control over to the native system.
  5. Resolve the long-standing issues with existing RES for Web without modifying the ResponderEventPlugin used by React Native.

We may not be able to exactly reproduce the behavior of the ResponderEventPlugin in user-space (e.g., order of events vs those coming from SimpleEventPlugin) but this doesn’t seem to be essential or reason enough to maintain the status quo. The downsides of not integrating with React's event system can hopefully be avoided in the future with new APIs added to ReactDOM.

Plan and timeline

The RES relies on listening to a handful of events at the top level, which is not something that ReactDOM supports outside of the Plugin system. Therefore, this replacement will need to bypass React and attach native event listeners. This isn't ideal for Concurrent Mode, but could be a surface for developing and testing new APIs attached to the modern event system @trueadm has been working on for ReactDOM.

The intended use for the RES Hook is as follows:

function View(props) {
  const ref = useRef(null);
  
  useResponderEvents(ref, {
    onStartShouldSetResponder: props.onStartShouldSetResponder,
    onResponderGrant: props.onResponderGrant,
    ...
  });
  
  return (
    <div ref={ref} />
  );
}

February 2020: Hooks rewrite

Rewriting the react-native-web components to use Hooks is a prerequisite for the other changes. Using hooks simplifies the implementations of components and has already resolved a few bugs in the library related to using setNativeProps and TextInput events. Once Hooks can be used the RES implementation can be exposed as a custom Hook.

March 2020: Responder Event System rewrite

The user-space implementation of the RES for web requires the existing ResponderEventPlugin unit tests to be ported and expanded upon. The replacement unit tests will test the public API and avoid dependencies on React internals (e.g., Fiber structures). Addition scenarios will also be covered: using either a mouse or touch, and terminating the responder when the native system should be in control.

April 2020: Remove unstable-native-dependencies

Once the RES rewrite lands in react-native-web we can safely remove the unstable-native-dependencies export and bundle from ReactDOM.

Metadata

Metadata

Assignees

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions