callstack / callstack/react-native-bottom-tabs

[Android] Content doesn't resize when tab bar visibility changes (tabBarHidden)

Open
#557 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
TypeScript
Stars
1.5k
Forks
109
Avg merge
11h 44m
Merged PRs (30d)
8

Description

### Before submitting a new issue

- [x] I tested using the latest version of the library, as the bug might be already fixed.
- [x] I tested using a supported version of react native.
- [x] I checked for possible duplicate issues, with possible answers.

### Bug summary

On Android, when `tabBarHidden` is toggled to `true`, the native bottom navigation view is set to `GONE` and its space is reclaimed at the native level, but the React content does not grow to fill it. The screen keeps its old height, leaving a blank strip at the bottom exactly the height of the tab bar. Bottom-anchored elements (`position: 'absolute', bottom: 0`) end up floating ~80dp above the actual bottom of the screen. Toggling back to visible has the same problem in reverse until something else triggers a relayout.

I tracked it down to the layout listener in `RCTTabView.kt`. The size-change guard compares the bounds of the whole container (`ReactBottomNavigationView`), but when only the tab bar visibility changes, the container keeps the exact same size — only the inner `layoutHolder` grows or shrinks:

```kotlin
post {
addOnLayoutChangeListener { _, left, top, right, bottom, _, _, _, _ ->
val newWidth = right - left // container size — unchanged when tab bar is hidden
val newHeight = bottom - top

onTabBarMeasuredListener?.invoke(...)

if (newWidth != lastReportedSize?.width || newHeight != lastReportedSize?.height) {
// never reached in this case, so onNativeLayoutListener doesn't fire
onNativeLayoutListener?.invoke(dpWidth, dpHeight)
lastReportedSize = Size(newWidth, newHeight)
}
}
}
```

Two problems compound here:

1. The guard compares the container size, while the value actually reported to JS is the `layoutHolder` size. When the tab bar is hidden, the container stays the same but `layoutHolder` changes, so the guard filters the update out.
2. The listener is attached to the container, and `OnLayoutChangeListener` only fires when that view's own bounds change — which they don't in this scenario. So even the callback itself may not run.

As a result `onNativeLayout` never reaches the JS side, `setMeasuredDimensions` keeps the stale height, and the children rendered by `TabView` stay at the old size.

Verified on an emulator with `uiautomator dump`: after hiding the tab bar, the native `layoutHolder` grows to full screen height (2400px on my device), while the React subtree inside it stays at the previous 2126px.

iOS is not affected.

### Library version

1.4.0

### Environment info

```shell
System:
OS: macOS 26.5.2
CPU: (18) arm64 Apple M5 Pro
Binaries:
Node: 24.13.0
Yarn: 3.6.4
npm: 11.6.2
npmPackages:
react: 19.2.3
react-native: 0.86.0
Android:
hermesEnabled: true
newArchEnabled: true
iOS:
hermesEnabled: true
newArchEnabled: true
```

### Steps to reproduce

1. Android app using `createNativeBottomTabNavigator` (New Architecture).
2. Render a screen with a bottom-anchored view, e.g. an action bar with `position: 'absolute', bottom: 0`.
3. Toggle `tabBarHidden` to `true` (typical case: entering a selection/edit mode).
4. The tab bar disappears, but the bottom-anchored view stays where the tab bar's top edge used to be, with an empty strip below it.

Tested on an Android 16 emulator (arm64), RN 0.86, and reproduced consistently.

### Reproducible sample code

```tsx
const [hidden, setHidden] = useState(false);

{/* screen content */}

setHidden(v => !v)} />

```

I have a fix working locally (report the `layoutHolder` size instead, and also listen to `layoutHolder` layout changes so the visibility-toggle case is caught). Will open a PR.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.