はじめに
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に渡します。
コードに表すと以下のようになります。
export const ScreenB: FC = () => {
// ...userIdsを取得
// ScreenAに遷移するとき、userIdsをパラメータで渡す
navigation.navigate('ScreenA', { userIds })
// ...
}
ScreenAでパラメータにアクセスしてみると、userIdsが正しく渡ってきていることを確認できました🎉
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」画面があるとします。
{
postTitle: 'An amazing post',
postBody: 'Amazing content for amazing post'
}
そして、navigation.navigate('Post', { postTitle: 'An okay post' })
で遷移すると、以下のパラメータを持つことになります。
postTitleは値が更新されていますが、postBodyの値はそのまま引き継がれています。
{
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,
});
では、これを踏まえて問題のコードを修正してみましょう。
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'>;
終わりに
ライブラリのデフォルトの挙動に振り回されてしまいました😢
画面遷移する時はパラメーターが上書きされてしまう仕様だと覚えておきます💡