Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 0 additions & 11 deletions __mocks__/react-native.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,17 +87,6 @@ jest.doMock('react-native', () => {
dimensions = newDimensions;
},
},

// `runAfterInteractions` method would normally be triggered after the native animation is completed,
// we would have to mock waiting for the animation end and more state changes,
// so it seems easier to just run the callback immediately in tests.
InteractionManager: {
...ReactNative.InteractionManager,
runAfterInteractions: (callback: () => void) => {
callback();
return {cancel: () => {}};
},
},
},
ReactNative,
) as ReactNativeMock;
Expand Down
2 changes: 1 addition & 1 deletion contributingGuides/INP_IMPROVEMENT_WORKFLOW.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ The most common ways to improve a component's performance are:
*Examples: [#89120](https://github.com/Expensify/App/pull/89120), [#86865](https://github.com/Expensify/App/pull/86865)*

> [!WARNING]
> Do not reach for `InteractionManager.runAfterInteractions` as a deferral primitive. It is being removed from React Native and is in the process of being migrated out of the codebase. New usages should not be introduced. See [INTERACTION_MANAGER.md](https://github.com/Expensify/App/blob/main/contributingGuides/INTERACTION_MANAGER.md) for more details.
> Do not reach for `InteractionManager.runAfterInteractions` as a deferral primitive. It is deprecated in React Native and now ships as a no-op stub, and it has been migrated out of the codebase. New usages must not be introduced - use `TransitionTracker` or the `afterTransition` callbacks built on it. See [INTERACTION_MANAGER.md](https://github.com/Expensify/App/blob/main/contributingGuides/INTERACTION_MANAGER.md) for more details.

2. Defer non-critical work past the next paint.

Expand Down
20 changes: 12 additions & 8 deletions contributingGuides/INTERACTION_MANAGER.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,17 @@

## Why

`InteractionManager` is being removed from React Native. We currently maintain a patch to keep it working, but that's a temporary measure and upstream libraries will also drop support over time.
`InteractionManager` is being removed from React Native. It is already deprecated, and as of RN 0.86 the shipped implementation is a no-op stub: `runAfterInteractions` falls through to `setImmediate`, `createInteractionHandle` returns `-1`, and `clearInteractionHandle` does nothing. Nothing can block the queue anymore.

Rather than keep patching, we're replacing `InteractionManager.runAfterInteractions` with purpose-built alternatives that are more precise.
We used to carry patches that restored the real implementation (`react-native+…+restore-interaction-manager.patch`) and that opened interaction handles during screen transitions (`@react-navigation+native-stack+…+added-interaction-manager-integration.patch`). Both patches have been removed - the app no longer calls `InteractionManager` anywhere, so there was nothing left for them to serve.

`InteractionManager.runAfterInteractions` has been replaced with purpose-built alternatives that are more precise.

## Current state

`runAfterInteractions` is used across the codebase for a wide range of reasons: waiting for navigation transitions, deferring work after modals close, managing input focus, delaying scroll operations, and many other cases that are hard to classify.
Application code no longer uses `runAfterInteractions`.

Historically `runAfterInteractions` was used across the codebase for a wide range of reasons: waiting for navigation transitions, deferring work after modals close, managing input focus, delaying scroll operations, and many other cases that are hard to classify.

## The problem

Expand Down Expand Up @@ -83,15 +87,15 @@ For reference, here's how the available timing primitives compare:

### `InteractionManager.runAfterInteractions` (legacy — do not use)

- React Native-specific. Fires after all **ongoing interactions** (animations, touches) complete
- Tracks interactions via `createInteractionHandle()` — anything that calls `handle.done()` unblocks the queue
- In practice, this means "run after the current navigation transition finishes"
- Problem: it's a global queue with no granularity — you can't say "after _this specific_ transition"
- React Native-specific. Deprecated upstream, the current RN implementation is a stub, so it no longer waits for anything - it is just `setImmediate` with extra steps
- Historically it fired after all **ongoing interactions** (animations, touches) completed, tracked via `createInteractionHandle()` - clearing the last handle unblocked the queue
- In practice that meant "run after the current navigation transition finishes"
- Problem: it was a global queue with no granularity — you couldn't say "after _this specific_ transition"

### Summary

| | Timing | Granularity | Platform |
| ---------------------- | ------------------------- | ------------------------- | --------------------- |
| `rAF` | Next frame (~16ms) | None — just "next paint" | Web + RN |
| `requestIdleCallback` | When idle (unpredictable) | None — "whenever free" | Web + RN (polyfilled) |
| `runAfterInteractions` | After animations finish | Global — all interactions | RN only |
| `runAfterInteractions` | Same as `setImmediate` (stubbed) | None - nothing blocks the queue | RN only |
2 changes: 1 addition & 1 deletion contributingGuides/NAVIGATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ Navigation.dismissModalWithReport({
> Why do we need a separate method to open a report from a modal?
>
> 1. On a narrow screen, we do not want to perform two operations: closing the modal and opening the report. This would cause two actions to be displayed on the screen, which could be confusing for users. Instead of two operations, we perform a replace on the modal, thanks to which there is a smooth transition to the report with simultaneous closing of the modal.
> 2. On a wide screen, we need to be sure that the modal has been closed before we want to navigate to the report. For this purpose, `navigate` called after `dismissModal` is wrapped in `InteractionManager.runAfterInteractions`.
> 2. On a wide screen, we need to be sure that the modal has been closed before we want to navigate to the report. For this purpose, `navigate` is passed as the `afterTransition` callback to `dismissModal`, so it only runs once the dismiss transition has completed (tracked via `TransitionTracker`).

### Summary

Expand Down
16 changes: 0 additions & 16 deletions patches/react-native/details.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,22 +146,6 @@
- E/App issue: [#69005](https://github.com/Expensify/App/issues/69005)
- PR introducing patch: [#69004](https://github.com/Expensify/App/pull/69004)

### [react-native+0.86.0+020+restore-interaction-manager.patch](react-native+0.86.0+020+restore-interaction-manager.patch)

- Reason:

```
This patch restores the old InteractionManager behavior. React Native 0.80 deprecated InteractionManager and modified
it to behave like `setImmediate`, more info here - https://github.com/facebook/react-native/blob/d9262c60f4c02d66417008970dc9c34b742aaa75/CHANGELOG.md?plain=1#L597

We need to restore the previous behavior to avoid introducing any bugs in the app.
Bug example - https://github.com/Expensify/App/pull/69535#issuecomment-3443059319
```

- Upstream PR/issue: There won't be any upstream changes. We need to get rid of InteractionManager
- E/App issue: https://github.com/Expensify/App/issues/71913
- PR introducing patch: https://github.com/Expensify/App/pull/69535

### [react-native+0.86.0+021+perf-increase-initial-heap-size.patch](react-native+0.86.0+021+perf-increase-initial-heap-size.patch)

- Reason: This patch increases the initial heap size of the Hermes runtime. This allows us to disable Hermes Young-Gen Garbage Collection (GC) in a separate patch, which improves initial TTI and app startup time.
Expand Down
Loading
Loading