callstack / callstack/react-native-paper

Snackbar stays mounted after `visible=false` under new architecture (iOS + Android)

オープン 初心者向け
#4,951 コメント 0 件 リアクション 0 件 担当者 0 名 GitHub で見る
主要言語
TypeScript
スター
14.5k
フォーク
2.2k
平均マージ
5日 23時間
マージ済み PR(30日)
12

説明

## Summary

Under React Native's new architecture (Fabric), paper's `` can get stuck mounted at non-zero opacity after `visible` is set to `false`. The hide animation runs visually, but the component never unmounts and stays visible on screen until something else forces a remount.

Sister bug to #3775 / #4445 (which were Android-only show path, fixed by #4447). This is the **hide path**, and reproduces on **both iOS and Android** with the new arch enabled.

## Root cause

[`src/components/Snackbar.tsx`](https://github.com/callstack/react-native-paper/blob/main/src/components/Snackbar.tsx#L219-L223), `handleOnHidden`:

```tsx
Animated.timing(opacity, {
toValue: 0,
duration: 100 * scale,
useNativeDriver: true,
}).start(({ finished }) => {
if (finished) { // ← this guard is the problem under Fabric
setHidden(true);
}
});
```

Under Fabric, this `start({finished})` callback fires with `finished: false` even when nothing has interrupted the animation. `setHidden(true)` is the only thing that makes the component return `null` and actually unmount, so the snackbar stays rendered at whatever opacity the (visually-running) animation reached.

The parallel show-path fix #4447 split `handleOnVisible` from `animateShow` to work around the same Fabric noise. The hide path was not touched and still carries the guard.

## Expected behaviour

After `visible={false}`, the Snackbar component unmounts cleanly (`hidden` flips to `true`) once the hide animation completes, regardless of the `finished` flag.

## Current behaviour

After `visible={false}`:
- The hide animation fires (opacity 1 → 0).
- The `start` callback fires with `finished: false`.
- `setHidden(true)` is skipped.
- The component stays mounted; nothing in the React state can clear it.
- Subsequent `setVisible(false)` taps re-trigger the same broken animation; the snackbar never goes away.

## Repro

1. New arch enabled.
2. Mount a global ` setVisible(false)} action={{ label: 'Action', onPress: () => {} }}>Message`.
3. `setVisible(true)`, then `setVisible(false)`.
4. Snackbar stays on screen.

Reproduced on:
- iOS 18 simulator (iPhone 15)
- iOS 26 simulator (iPhone 17 Pro Max)
- Android Pixel 8 emulator (API 35)

All with `newArchEnabled: true`.

## Environment

- `react-native-paper`: 5.15.2
- `react-native`: 0.83.6
- Expo SDK 55
- Fabric / new arch: enabled

## Proposed fix

Drop the `if (finished)` guard, mirroring how #4447 worked around the same class of Fabric noise on the show path:

```diff
Animated.timing(opacity, {
toValue: 0,
duration: 100 * scale,
useNativeDriver: true,
- }).start(({ finished }) => {
- if (finished) {
- setHidden(true);
- }
+ }).start(() => {
+ setHidden(true);
});
```

### Trade-off

Under a *genuine* interrupt (a new show kicked off mid-hide), `setHidden(true)` would now also fire. The next render uses the new `visible=true` path → `setHidden(false)` → re-animates show. The visible artefact is at worst a one-frame snap. A stuck Snackbar that no state change can clear is strictly worse.

Happy to open a PR if useful. Tested locally via `patch-package`; resolves the issue on both platforms.

コントリビューションガイド

コントリビューションガイドを開く

調査の方向性

Read src/components/Snackbar.tsx, focusing on handleOnHidden and the hide animation callback; compare its behavior with the show-path workaround in #4447. Reproduce with Fabric enabled by toggling visible from true to false on iOS or Android. Done means the hide animation leaves the Snackbar unmounted and hidden even when the callback reports finished: false.

索引モデルが issue の本文から書いたものです。

評価

技術スタック
react-native, typescript
領域
mobile
issue の種類
バグ
難易度
2/5
見積もり時間
1〜3時間
活発さ
静か
明瞭さ
明確に書かれている
初心者へのやさしさ
78/100

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。