📑

React Navigation を使って、ボトムタブが押されたら画面上端までスクロールするUIを作る

2021/04/23に公開

React Native 製のアプリで、画面遷移を制御するために React Navigation を使っている場合に役立つ tips を紹介します。

以下のスクリーンショットのように、フォーカスされているタブのボトムタブが押されると、画面の最上部までスクロールする UI を作りたいとします。

ボトムタブがタップされると、

上部までスクロール。

React Navigation には、この UI を実現するための便利な API が用意されています。
それは useScrollToTop です。

ScrollView か Flatlist と組み合わせて使うことができます。

関数コンポートの内部で使う場合のコードサンプル

import * as React from 'react';
import { ScrollView } from 'react-native';
import { useScrollToTop } from '@react-navigation/native';

function Albums() {
  const ref = React.useRef(null);

  useScrollToTop(ref);

  return <ScrollView ref={ref}>{/* content */}</ScrollView>;
}

サンプルはリンク先の公式ドキュメントからお借りしました。
リンク先には、クラスコンポーネントで使う場合のサンプルも記載されています。

注意点

以下のように、ボトムタブの内部でトップタブを使っている場合には注意が必要です。

ボトムタブ内にトップタブがあると、 ボトムタブが押されてもスクロールしません。

実はトップタブ(今回の例ではタブ1と書かれたタブ)が押された時にスクロールが発生します。

useScrollToTop の実装を見ると、階層上最も近いタブナビゲータに対して tabPress イベントのリスナーを設定していることがわかります。そのため、最も近かったトップタブが押されるとスクロールする挙動になっていました。

useScrollToTop.ts(抜粋)

export default function useScrollToTop(
  ref: React.RefObject<ScrollableWrapper>
) {
  const navigation = useNavigation();
  const route = useRoute();

  React.useEffect(() => {
    let current = navigation;

    // The screen might be inside another navigator such as stack nested in tabs
    // We need to find the closest tab navigator and add the listener there
    while (current && current.getState().type !== 'tab') {
      current = current.getParent();
    }

    if (!current) {
      return;
    }

    const unsubscribe = current.addListener(
      // We don't wanna import tab types here to avoid extra deps
      // in addition, there are multiple tab implementations
      // @ts-expect-error
      'tabPress',
      
      ...

トップタブと併用しているケースでもボトムタブが押されたらスクロールさせたければ、 useScrollToTop に頼らず自分でスクロールを制御する必要がありそうです。

まとめ

React Navigation の useScrollToTop を紹介しました。便利な関数が提供されていて有り難いです。

Discussion