1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

この記事誰得? 私しか得しないニッチな技術で記事投稿!
Qiita Engineer Festa20242024年7月17日まで開催中!

【React Navigation 6】画面遷移時に既存のパラメータを保持したいときは`merge: true` オプションを指定しよう

Posted at

はじめに

React Nativeの開発で、画面遷移にはReact Navigationのnavigate関数を使用しています。第2引数としてパラメータを遷移先の画面に渡すことができます。

この仕組みを使って実装していたところ、なんか思ってたんと違う😕挙動を確認できました。調べてみたところ、デフォルトでその動きになるように仕様が変わっていたようなのでまとめます。

先に結論を書きます。

  • React Navigation 6系以降では、画面遷移時にパラメータがデフォルトで上書きされるようになった
  • 既存のパラメータを維持したい場合は、navigate関数を使用する際にmerge: trueオプションを指定する
navigation.navigate({
  name: 'ScreenName',
  params: { newParam: 'value' },
  merge: true
});

発生した問題

ScreenAとScreenBの2画面があります。
ScreenAからScreenBに移動し、ScreenBでuserIdsを取得します。その後、ScreenBからScreenAに戻ります。このとき、取得したuserIdsをパラメータとしてScrrenAに渡します。
コードに表すと以下のようになります。

ScreenB
export const ScreenB: FC = () => {
// ...userIdsを取得

// ScreenAに遷移するとき、userIdsをパラメータで渡す
 navigation.navigate('ScreenA', { userIds })
  
// ...
}

ScreenAでパラメータにアクセスしてみると、userIdsが正しく渡ってきていることを確認できました🎉

ScreenA
import { useRoute } from '@react-navigation/native'
import { Text } from 'react-native-svg'

export const ScreenA: FC = () => {
 const route = useRoute<ScreenARouteProp>()
// ...省略
 const { name, userIds } = route.params

 return (
    <Text>{name}</Text>
 )
}

しかし、思わぬところで事件が起きます。
初めてScreenAが開かれるときは、ScreenCから遷移してきます。このときScreenCからパラメーターとしてnameを受け取っています。
このnameを画面に描画しているのですが、ScreenBからScreenAに遷移したとき、表示できていたはずのnameが表示されなくなってしまいました😱

原因

この原因は、React Navigationの仕様によるものです。

公式ドキュメントによると、6系から画面遷移時にパラメータはマージではなく上書きされるようになったらしいです。

以下、公式ドキュメントの例を参考に仕様をまとめます。

5系以前の仕様では、新しいパラメータが既存のパラメータにマージされていました。
マージとは、新しいパラメータと既存のパラメータが結合されることを意味します。このとき、キーが重複する場合は新しい値で上書きされ、新しいパラメータで指定されていないプロパティは、既存の値が保持されます。

例えば、以下のパラメータを持つ既存の「Post」画面があるとします。

Post
{
  postTitle: 'An amazing post',
  postBody: 'Amazing content for amazing post'
}

そして、navigation.navigate('Post', { postTitle: 'An okay post' })で遷移すると、以下のパラメータを持つことになります。
postTitleは値が更新されていますが、postBodyの値はそのまま引き継がれています。

Post
{
  postTitle: 'An okay post',
  postBody: 'Amazing content for amazing post'
}

この仕様は、ユーザーからのバグ報告が多発してしまったみたいです。
パラメーターを渡していないのに、元のパラメータが保持されてしまうのは気持ち悪いということでしょうか…😕

6系からはこの仕様が変更になりました。
パラメータがマージされず、新しいパラメータが既存のすべてのパラメータを上書きするようになりました。
よって、navigation.navigate('Post', { postTitle: 'An okay post' })でナビゲートすると、元のpostBodyプロパティは消えて以下のパラメータを持つことになります。

{
  postTitle: 'An okay post'
}

問題の実装を振り返ってみましょう。
screenBからScreenAに遷移するとき、navigation.navigate('ScreenA', { userIds })として、新しく{ userIds }を渡しています。
これにより、元々ScreenAが保持していたパラメータが上書きされ、nameプロパティが消えてしまったので描画できなくなってしまったというわけです。

解決

6系からはデフォルトでパラメータがマージされず、新しいパラメータが既存のすべてのパラメータを上書きしてしまいますが、必要に応じてこの設定を変更することができます!!
navigateにmerge: trueを含むオブジェクトを渡すことでパラメータをマージできます🎉
以下のように、merge: true オプションを指定しましょう。

navigation.navigate({
  name: 'Post',
  params: { postTitle: 'An okay post' },
  merge: true,
});

では、これを踏まえて問題のコードを修正してみましょう。

ScreenB
 navigation.navigate({
    name: 'ScreenA',
    params: { userIds },
    merge: true
  });

これで、screenAが保持していたnameプロパティは上書きされずそのまま保持され、新しく渡ってきたuserIdsはパラメータに追加されます。

余談

useRouteの型引数ScreenAParamsは以下のように指定しています。
ScreenBから遷移するときは、nameプロパティは渡されないのでundefindとなります。
merge: trueを指定しない場合、nameプロパティはundefindとして上書きされてしまいました。

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

type ScreenAParams = 
 | {
     // ScreenCからの遷移
      userIds?: never
      name: string
    }
  | {
     // ScreenBからの遷移
      userIds: string[]
      name?: never
    };

type ScreenARouteProp = RouteProp<{ ScreenA: ScreenAParams }, 'ScreenA'>;

終わりに

ライブラリのデフォルトの挙動に振り回されてしまいました😢
画面遷移する時はパラメーターが上書きされてしまう仕様だと覚えておきます💡

1
1
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
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?