3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

React Navigationにおける画面遷移時の再レンダリングとuseEffectの発火について

Last updated at Posted at 2024-02-24

はじめに

当社のプロダクトは、WEBアプリケーションとスマホアプリの両方を提供しています。
フロントエンドは、ReactでWEB版、React Nativeでスマホ版を開発しています。機能を追加する際は、まずWEB版を実装し、その後スマホ版を実装します。

画面遷移の実装で、WEB版ではうまく動作していたのですが、React Nativeで問題が起きてしまいました。その原因と解決方法を共有します。

本題

この記事で伝えたいことはこちらです。

  • React Navigationの go back()で画面遷移する場合、再レンダリングは行われない
  • useIsFocusedを使うことで画面が最上部に来たことを検知して再レンダリングを行うため、画面遷移したタイミングでuseEffectを発火させることができる

要件

  • 2つの画面(A、B)を実装する
  • 通常は画面Aから画面Bへ遷移する
  • 画面Aでは初回描画時にデータをfetchし、表示する
  • 画面Bに、画面Aに戻る×ボタンを追加する

設計

  • 画面B=>Aの遷移はReact Navigationのgo back()を使う
  • 画面B=>Aに遷移したときにも画面AのuseEffectを発火させる

問題

React NatvigationのgoBack()を使って1つ前の画面に遷移したところ、画面Aの初回描画時のfetch処理が実行されませんでした。

ScreenA
const ScreenA = () => {
  useEffect(() => {
  
   // ...fetch処理
  }, [])
};
ScrrenB
import { useNavigation } from '@react-navigation/native'

const ScreenB = () => {
    const navigation = useNavigation()
    
    const onClose = (): void => {
        navigation.goBack()
    };
    
    return <button onPress={onClose}>×</button>;
};

なーぜー??????どこか間違えている??????
わからなかったので、先輩に質問しました。

原因

useEffectが発火しなかったのは、React Navigationの画面遷移の仕様が原因でした。

React Navigationでは画面遷移をStackで管理しています。画面を層としてイメージするのが個人的にしっくりきています。層が上に積み上がっていくようなイメージです。
goBack()で前の画面に戻る時は、1番上に積み上がっていた画面が剥がれてその下にいた画面が1番上に出てくるようなイメージになります。

画面遷移.001.jpeg

そしてこのgoBack()で画面遷移したときは再レンダリングが行われません。ただ画面が1番上に出てきただけで、新たに読み込まれて表示されるわけではないということです。したがって、遷移したときにuseEffectは発火しません。

React Routerとの違い

WEB版では画面遷移したタイミングでfetch処理が行われていました。
WEB版の画面遷移の実装は、React RouterのuseNavigateを使用しています。
引数に遷移する画面のURLを指定して画面遷移します。
このとき、遷移した画面は再レンダリングされるため、画面AのuseEffectは発火したというわけです。

import { Navigate, useNavigate } from 'react-router-dom'

const ScreenB = () => {
    const navigate = useNavigate()
    
    const onClose = (): void => {
      navigate('https://.../screenA')
    }
    
    return <button onClick={onClose}>×</button>;
};

解決

useIsFocuseを使うことで、解決できます🎉
目的は、useEffectの依存配列に画面遷移したことを検知するstateを入れて、遷移したタイミングで発火させることです。

useIsFocused

useIsFocusedはReact Navigationが提供しているhooksです。
「現在その画面が開かれているか否か?」 を判定し、返り値はbooleanの値です。デフォルトはfalseですが、その画面が1番上で開かれた場合はtrueに変更されます。
このhooksを使えば、画面が最上部に来たことを検知することができます。

import { useIsFocused } from '@react-navigation/native';

// ...

function Profile() {
  const isFocused = useIsFocused();

  return <Text>{isFocused ? 'focused' : 'unfocused'}</Text>;
}

このhooksを使ってisFocusedというstateをuseEffectの依存配列に入れれば、画面が開かれた時はfalse=>trueに変更されるので変更を検知してuseEffectが発火します。

修正

画面AでuseIsFocusedをimportして、isFocusedを定義し、画面遷移したときに発火させたいuseEffectの依存配列に入れました。

ScreenA
import { useIsFocused } from '@react-navigation/native';

// 画面B=>Aに遷移したときもfetchできるようにする
const ScreenA = () => {
  const isFocused = useIsFocused()

  useEffect(() => {
    if (!isFocused) {
      return
    }

   // ...fetch処理
  }, [isFocused])
};

余談

useIsFocusedを使う以外の方法も教えていただきました。
画面遷移するときにnavigationのパラメーターに何かstateを渡してそれを検知する方法です。
React Navigationで遷移する際、navigate関数の第2引数に、遷移先の画面に渡したいパラメーターをオブジェクトに入れて指定することができます。

// 遷移先の画面に渡したいパラメーターを指定する
  navigation.navigate('Details', {
    itemId: 86,
    otherParam: 'anything you want here',
  });

// 遷移先の画面で渡されたパラメーターを読み取る
   const { itemId, otherParam } = route.params;

このパラーメーターに例えばUUIDなどランダムな文字列を生成して指定します。すると、画面遷移するたびに異なるUUIDを生成することができるので、useEffectの依存配列にUUIDを含めれば、変更を検知してuseEffectを発火させることができます。

しかし、useEffectを発火されるためだけにそこまでするか?というのが正直なところなので、今回は採用しませんでした。

まとめ

  • React Navigationの go back()で画面遷移する場合、再レンダリングは行われない
  • useIsFocusedを使うことで画面が最上部に来たことを検知して再レンダリングを行うため、画面遷移したタイミングでuseEffectを発火させることができる

React Navigationでは画面遷移時の動作がReact Routerと異なるため、注意が必要です。
再レンダリングが必要な場合はuseIsFocusedを活用しましょう。

参考

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?