|
| 1 | +# Native Event Propagation |
| 2 | + |
| 3 | +React Native declares, for each native (host) component, which events **bubble** up the tree and which are **direct**, meaning they are delivered only to the element that emitted them. Use this reference when deciding whether an event helper should look for handlers on ancestor elements. |
| 4 | + |
| 5 | +## How to read this |
| 6 | + |
| 7 | +- Native event names use a `top` prefix that maps to the `on*` prop: `topLayout` → `onLayout`. The tables below use the short name (`layout`). |
| 8 | +- Every host component inherits the **base view config** events and adds its own component-specific events on top of them. |
| 9 | +- `fireEvent` walks up the tree to find a handler, which matches bubbling events. Events for which `isDirectEvent()` in `src/fire-event.ts` returns `true` skip that walk and invoke only the target element's handler. Today only `layout` is treated as direct. |
| 10 | +- Snapshot taken from `react-native@0.88.0-rc.1`. See [Sources](#sources) to re-check after RN upgrades. |
| 11 | + |
| 12 | +## Base view config (all host components) |
| 13 | + |
| 14 | +| Kind | iOS | Android | |
| 15 | +| -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | |
| 16 | +| Bubbling | `press`, `click`, `change`, `focus`, `blur`, `submitEditing`, `endEditing`, `keyPress`, `touchStart`, `touchMove`, `touchEnd`, `touchCancel`, `pointer*`\* | `click`, `change`, `select`, `focus`, `blur`, `keyDown`, `keyUp`, `touchStart`, `touchMove`, `touchEnd`, `touchCancel`, `pointer*`\* | |
| 17 | +| Direct | `layout`, `accessibilityAction`, `accessibilityTap`, `magicTap`, `accessibilityEscape` | `layout`, `accessibilityAction`, `scroll`, `scrollBeginDrag`, `scrollEndDrag`, `momentumScrollBegin`, `momentumScrollEnd`, `contentSizeChange`, `selectionChange`, `message`, `loadingStart`, `loadingFinish`, `loadingError` | |
| 18 | + |
| 19 | +\* `pointer*` = `pointerDown`, `pointerMove`, `pointerUp`, `pointerCancel`, `pointerEnter`, `pointerLeave`, `pointerOver`, `pointerOut`, `gotPointerCapture`, `lostPointerCapture`. |
| 20 | + |
| 21 | +Both platforms also register `onGestureHandlerEvent` and `onGestureHandlerStateChange` as direct events for React Native Gesture Handler. |
| 22 | + |
| 23 | +## Component-specific events |
| 24 | + |
| 25 | +Events listed here are added on top of the base view config. "Host name" is the native `uiViewClassName` (or codegen component name). |
| 26 | + |
| 27 | +| Component | Host name | Bubbling | Direct | |
| 28 | +| ---------------------------------- | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------- | |
| 29 | +| `View`, `Pressable`, etc. | `RCTView` | — | — | |
| 30 | +| `Text` | `RCTText` (nested: `RCTVirtualText`, no extra events) | — | `textLayout` | |
| 31 | +| `TextInput` (iOS) | `RCTSinglelineTextInputView`, `RCTMultilineTextInputView` | `blur`, `change`, `endEditing`, `focus`, `keyPress`, `submitEditing`, `touchMove`, `touchEnd`, `touchCancel` | `scroll`, `selectionChange`, `contentSizeChange`, `changeSync`, `keyPressSync` | |
| 32 | +| `TextInput` (Android) | `AndroidTextInput` | `endEditing`, `keyPress`, `submitEditing` | `scroll` | |
| 33 | +| `ScrollView` | `RCTScrollView` | — | `scroll`, `scrollBeginDrag`, `scrollEndDrag`, `momentumScrollBegin`, `momentumScrollEnd`; iOS also `scrollToTop` | |
| 34 | +| `ScrollView` (horizontal, Android) | `AndroidHorizontalScrollView` | — | — (scroll events come from the Android base config) | |
| 35 | +| `Image` | `RCTImageView` | — | `loadStart`, `progress`, `error`, `load`, `loadEnd`; iOS also `partialLoad` | |
| 36 | +| `Switch` (iOS) | `Switch` | `change` | — | |
| 37 | +| `Switch` (Android) | `AndroidSwitch` | `change` | — | |
| 38 | +| `Modal` | `ModalHostView` | — | `requestClose`, `show`, `dismiss`, `orientationChange` | |
| 39 | +| `RefreshControl` (iOS) | `PullToRefreshView` | — | `refresh` | |
| 40 | +| `RefreshControl` (Android) | `AndroidSwipeRefreshLayout` | — | `refresh` | |
| 41 | +| `DrawerLayoutAndroid` | `AndroidDrawerLayout` | — | `drawerSlide`, `drawerStateChanged`, `drawerOpen`, `drawerClose` | |
| 42 | + |
| 43 | +## Known gaps in RNTL |
| 44 | + |
| 45 | +These events are direct in React Native but still bubble through `fireEvent`. Changing them is a breaking change for users who fire them on a child element: |
| 46 | + |
| 47 | +- Scroll events: `scroll`, `scrollBeginDrag`, `scrollEndDrag`, `momentumScrollBegin`, `momentumScrollEnd` |
| 48 | +- `contentSizeChange`, `selectionChange`, `textLayout` |
| 49 | +- `Image` load events, `Modal` events, `refresh` |
| 50 | + |
| 51 | +## Sources |
| 52 | + |
| 53 | +All paths are relative to `node_modules/react-native`: |
| 54 | + |
| 55 | +- Base config: `Libraries/NativeComponent/BaseViewConfig.ios.js`, `Libraries/NativeComponent/BaseViewConfig.android.js` |
| 56 | +- Static view configs: `Libraries/Text/TextNativeComponent.js`, `Libraries/Image/ImageViewNativeComponent.js`, `Libraries/Components/ScrollView/*NativeComponent.js`, `Libraries/Components/TextInput/RCTTextInputViewConfig.js`, `Libraries/Components/TextInput/AndroidTextInputNativeComponent.js` |
| 57 | +- Codegen specs (`DirectEventHandler` vs `BubblingEventHandler` prop types): `src/private/components/*/specs/*NativeComponent.js` |
0 commit comments