はじめに
Reactで無限スクロールを実装する際、次の画面へ遷移してからブラウザバックするとスクロール位置が初期位置に戻されてしまう問題が発生していました。この記事では、この問題の原因と解決策について触れていきます。
問題の原因
無限スクロールを実装したコンポーネントでは、画面遷移によってコンポーネントがアンマウントされてから、ブラウザバックにより再びマウントされます。つまりそこで再レンダリングが行われています。そのため、スクロール位置が初期化されていました。
解決策
今回はセッションストレージに状態を保存する方法を採用しました。保存したのは、
- 無限スクロール中に表示したアイテム
- 無限スクロールは継続中か判断するフラグ
- DynamoDBを使用していたのでlast_evaluated_keyの内容
- スクロール位置
以上、4点です。この内容を次の画面へ遷移する前にセッションに保存します。その後画面を遷移させます。
セッションに保存するロジック
画面遷移時にセッションストレージに保存します。
const handleItemClick = (caseItem) => {
const stateToSave = {
savedCases: cases,
savedHasMore: hasMore,
savedLastKey: lastKey,
scrollPosition: containerRef.current ? containerRef.current.scrollTop : 0,
};
sessionStorage.setItem('セッションに保存する時の名前', JSON.stringify(stateToSave));
navigate(`/***/***/${***}`);
};
セッションから取得するロジック
ページがロードされた際にセッションストレージからスクロール位置を取得し、復元します。
useLayoutEffect(() => {
const savedState = sessionStorage.getItem('セッションに保存した時の名前');
if (savedState) {
const { savedCases, savedHasMore, savedLastKey, scrollPosition } = JSON.parse(savedState);
setCases(savedCases);
setHasMore(savedHasMore);
setLastKey(savedLastKey);
if (containerRef.current) {
containerRef.current.scrollTop = scrollPosition;
}
}
}, []);
取得した内容を再レンダリングの内容と突合するロジック
ここで、セッションの内容と再レンダリング時に取得した内容とを比較して重複を削除します。
const removeDuplicates = (newCases, existingCases) => {
return newCases.filter(
(product) => !existingCases.some((caseItem) => caseItem.name === product.name)
);
};
TOPページに遷移した際のセッション削除
セッションなので、ブラウザを閉じれば削除されるものの、無限スクロールするアイテムが変更される場合にはセッションストレージからデータを削除してください。別のカテゴリのアイテムが混ざるとバグの原因になります。
useEffect(() => {
sessionStorage.removeItem('セッションに保存した時の名前');
}, []);
useLayoutEffectを採用した理由
この記事のコード例ではセッションから取得するロジックのところで、useLayoutEffect
を使用しています。useLayoutEffect
はDOMの更新が画面に描画される前に実行されるため、スクロール位置の復元など、描画に直接関連する処理を行う際に有用です。
まとめ
今回は無限スクロールを実装する際に発生するスクロール位置の初期化問題を、セッションストレージを使用して解決することができました。これでUXも向上したと思います。
そもそもではありますが、無限スクロールを採用する理由の1つに、ユーザーが没頭できるようにするというのが挙げられますが、ブラウザバックしたらスクロール位置が初期化されていては、ユーザーは没頭するどころかストレスを溜めてしまうでしょう。ユーザーフレンドリーなつくりにしたいものですね!
ではでは、今回はこのへんで👋