From 1a05d471c7a41dd255131f1a613ac34369f004f4 Mon Sep 17 00:00:00 2001 From: Yehor Kharchenko Date: Thu, 8 Oct 2026 12:56:12 +0200 Subject: [PATCH 1/2] add patch that replaces crash with soft exception --- patches/react-native/details.md | 8 +++++++ ...eViewAt-non-ViewGroup-parent-APP-H7A.patch | 21 +++++++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 patches/react-native/react-native+0.86.0+047+soft-fail-removeViewAt-non-ViewGroup-parent-APP-H7A.patch diff --git a/patches/react-native/details.md b/patches/react-native/details.md index 333184aca08a..43735d423d30 100644 --- a/patches/react-native/details.md +++ b/patches/react-native/details.md @@ -382,3 +382,11 @@ - E/App issue: https://github.com/Expensify/App/issues/102741 - PR introducing patch: https://github.com/Expensify/App/pull/102901 - 0.86.0 migration note: drop this patch once the adopted React Native release contains `361bc24`. + +### [react-native+0.86.0+047+soft-fail-removeViewAt-non-ViewGroup-parent-APP-H7A.patch](react-native+0.86.0+047+soft-fail-removeViewAt-non-ViewGroup-parent-APP-H7A.patch) + +- Reason: Fixes the fatal `IllegalStateException: Unable to remove a view from a view that is not a ViewGroup` behind `APP-H7A`. `SurfaceMountingManager.removeViewAt` throws when the parent tag has a `ViewState` whose view is null or not a `ViewGroup`. It is the `Remove` counterpart of `APP-K0G` (`+045`), with the same sources: a placeholder `ViewState` with no view created by `updateEventEmitter`, delayed deletes during touches, and differ flatten/unflatten bugs (`+046` closes one of them). `IntBufferBatchMountItem` is not retryable, so the throw kills the app. A view that is not a `ViewGroup` cannot hold children, so the removal is a no-op. The patch soft-logs a `ReactNoCrashSoftException` with the parent view class and returns, which is the upstream fix. If the child is still attached to a stale native parent, the next `addViewAt` of that child already detaches it (the "View already has a parent" branch), so nothing crashes later. The log message keeps 0.86.0's wording and adds `ParentView` the same way `+045` does. Unlike upstream, the patch also covers a null `parentView`: 0.86.0 has no `checkNotNull(parentView)` before this check, so null falls into the same branch, while upstream still crashes on it. +- Upstream PR/issue: [react-native#57860](https://github.com/react/react-native/pull/57860) / [react-native@3f553d7](https://github.com/react/react-native/commit/3f553d76bc3f4f57e62653d11bab415a21cd02a8) +- E/App issue: https://github.com/Expensify/App/issues/103369 +- PR introducing patch: TBD +- 0.86.0 migration note: the adopted release containing `3f553d7` is not enough to drop this patch. `0.87.0` still throws here and already has `checkNotNull(parentView)` right before this check. `0.88` soft-fails a non-`ViewGroup` parent but keeps that `checkNotNull`, so a null `parentView` (the placeholder `ViewState` from [react-native#58526](https://github.com/react/react-native/issues/58526)) still crashes. On the RN 0.87 upgrade, rewrite the patch to soft-fail both the `checkNotNull` and the throw. On the 0.88 upgrade, reduce it to soft-failing the `checkNotNull` only. Drop it once upstream handles a null `parentView` in `removeViewAt`, or once the `ParentView` value in these soft exceptions shows that null never happens. diff --git a/patches/react-native/react-native+0.86.0+047+soft-fail-removeViewAt-non-ViewGroup-parent-APP-H7A.patch b/patches/react-native/react-native+0.86.0+047+soft-fail-removeViewAt-non-ViewGroup-parent-APP-H7A.patch new file mode 100644 index 000000000000..4bd20b786a25 --- /dev/null +++ b/patches/react-native/react-native+0.86.0+047+soft-fail-removeViewAt-non-ViewGroup-parent-APP-H7A.patch @@ -0,0 +1,21 @@ +diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/SurfaceMountingManager.kt b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/SurfaceMountingManager.kt +--- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/SurfaceMountingManager.kt ++++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/SurfaceMountingManager.kt +@@ -442,10 +442,13 @@ + + val parentView = parentViewState.view + if (parentView !is ViewGroup) { +- val message = +- "Unable to remove a view from a view that is not a ViewGroup. ParentTag: $parentTag - Tag: $tag - Index: $index" +- FLog.e(TAG, message) +- throw IllegalStateException(message) ++ ReactSoftExceptionLogger.logSoftException( ++ TAG, ++ ReactNoCrashSoftException( ++ "Unable to remove a view from a view that is not a ViewGroup. ParentTag: $parentTag - Tag: $tag - Index: $index - ParentView: ${parentView?.javaClass?.simpleName}" ++ ), ++ ) ++ return + } + + if (SHOW_CHANGED_VIEW_HIERARCHIES) { From 7b23389d9bfc908999539873c6dc5c4ffd55cba5 Mon Sep 17 00:00:00 2001 From: Yehor Kharchenko Date: Thu, 8 Oct 2026 13:06:14 +0200 Subject: [PATCH 2/2] update details --- patches/react-native/details.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/patches/react-native/details.md b/patches/react-native/details.md index 43735d423d30..0d83081b6d09 100644 --- a/patches/react-native/details.md +++ b/patches/react-native/details.md @@ -388,5 +388,5 @@ - Reason: Fixes the fatal `IllegalStateException: Unable to remove a view from a view that is not a ViewGroup` behind `APP-H7A`. `SurfaceMountingManager.removeViewAt` throws when the parent tag has a `ViewState` whose view is null or not a `ViewGroup`. It is the `Remove` counterpart of `APP-K0G` (`+045`), with the same sources: a placeholder `ViewState` with no view created by `updateEventEmitter`, delayed deletes during touches, and differ flatten/unflatten bugs (`+046` closes one of them). `IntBufferBatchMountItem` is not retryable, so the throw kills the app. A view that is not a `ViewGroup` cannot hold children, so the removal is a no-op. The patch soft-logs a `ReactNoCrashSoftException` with the parent view class and returns, which is the upstream fix. If the child is still attached to a stale native parent, the next `addViewAt` of that child already detaches it (the "View already has a parent" branch), so nothing crashes later. The log message keeps 0.86.0's wording and adds `ParentView` the same way `+045` does. Unlike upstream, the patch also covers a null `parentView`: 0.86.0 has no `checkNotNull(parentView)` before this check, so null falls into the same branch, while upstream still crashes on it. - Upstream PR/issue: [react-native#57860](https://github.com/react/react-native/pull/57860) / [react-native@3f553d7](https://github.com/react/react-native/commit/3f553d76bc3f4f57e62653d11bab415a21cd02a8) - E/App issue: https://github.com/Expensify/App/issues/103369 -- PR introducing patch: TBD -- 0.86.0 migration note: the adopted release containing `3f553d7` is not enough to drop this patch. `0.87.0` still throws here and already has `checkNotNull(parentView)` right before this check. `0.88` soft-fails a non-`ViewGroup` parent but keeps that `checkNotNull`, so a null `parentView` (the placeholder `ViewState` from [react-native#58526](https://github.com/react/react-native/issues/58526)) still crashes. On the RN 0.87 upgrade, rewrite the patch to soft-fail both the `checkNotNull` and the throw. On the 0.88 upgrade, reduce it to soft-failing the `checkNotNull` only. Drop it once upstream handles a null `parentView` in `removeViewAt`, or once the `ParentView` value in these soft exceptions shows that null never happens. +- PR introducing patch: https://github.com/Expensify/App/pull/103447 +- 0.86.0 migration note: the adopted release containing `3f553d7` is not enough to drop this patch. `0.87.0` still throws here and already has `checkNotNull(parentView)` right before this check. `0.88` soft-fails a non-`ViewGroup` parent but keeps that `checkNotNull`, so a null `parentView` (the placeholder `ViewState` from [react-native#58526](https://github.com/react/react-native/issues/58526)) still crashes. On the 0.88 upgrade, reduce it to soft-failing the `checkNotNull` only. Drop it once upstream handles a null `parentView` in `removeViewAt`.