はじめに
ReactとMongoDBを使用した無限スクロールの実装中、ページネーションの境界でデータが重複して表示される問題に遭遇した。
この記事では、MongoDBのソート挙動に起因する問題の原因と、_id
を用いた具体的な解決策について解説する。
想定読者
- ReactとMongoDBを使用したアプリケーションを開発している方
- 無限スクロール・ページネーションをを実装している、もしくは実装予定の方
- データベースのソート挙動について知りたい方
結論から
MongoDBのソート挙動により、同一ソート値を持つドキュメントの順序が一貫性を欠く場合がある。
これにより、Reactの無限スクロール実装時にキー重複警告が発生する。
_id
フィールドを補助的なソートキーとして追加することで問題を解決できる。
開発環境
- React 18.x
- MongoDB 8.0.0
- MongoDB Node.js Driver 6.9.0
- TypeScript 5.x
- Node.js 23.5.0
問題の詳細
大量のデータが存在するMongoDBのCollectionに対してあるクエリでフィルターした結果を、無限スクロールで表示されるデータがダブった。
具体的には無限スクロールの1回目に取得した最後のデータと2回目に取得した最初のデータが重複した。
発生した警告
Warning: Encountered two children with the same key, `.$xxxxxxxxxxxx`.
Keys should be unique so that components maintain their identity across updates.
Non-unique keys may cause children to be duplicated and/or omitted — the behavior
is unsupported and could change in a future version.
問題の再現条件
以下の条件が揃った場合に発生:
- フィルター条件を含むMongoDBクエリ
- 昇順でのソート
- 2ページ目以降のデータ取得
問題のあるコード
// Backend: MongoDB query
const sortObject = {
[sortBy]: order === "asc" ? 1 : -1
};
const items = await Collection.find(query)
.sort(sortObject)
.skip(skip)
.limit(limit)
.lean()
.exec();
// Frontend: React component
const loadMore = async (page: number) => {
const response = await fetchItems(page);
setState((prev) => ({
items: [...prev.items, ...response.items], // appending returned items
page,
}));
};
実際はpage === 1
かどうかでitemsのappendなど調整しているが、詳細は一部省略する。(本題ではないので)
原因の特定
MongoDBのソート挙動の特徴
- 同一のソート値を持つドキュメントが存在する場合、その順序は必ずしも一貫性を持たない
- ページネーション(
skip
とlimit
)と組み合わさると、同一ドキュメントが異なるページに出現する可能性がある - これにより、React側で同一キーを持つアイテムが重複して表示される
解決策
補助的なソートキーとして_idを追加.
(参考) MongoDB Documentation: sort()
MongoDBクエリの修正
const sortObject = {
[sortBy]: order === "asc" ? 1 : -1,
_id: 1 // 補助的なソートキーとして_idを追加
};
const items = await Collection.find(query)
.sort(sortObject)
.skip(skip)
.limit(limit)
.lean()
.exec();
なぜ解決するのか
-
_id
フィールドはMongoDBにおいて一意性が保証されている - 主ソートキーが同値の場合でも、
_id
による順序付けで一貫性のある結果が得られる - これにより、ページネーション時の重複が防止される
技術的考察
MongoDBのソート挙動
- デフォルトでは、同一ソート値を持つドキュメントの順序は不定
- 分散システムやシャーディングの場合、この挙動はより顕著になる
- 安定したソートには必ず一意な値をソートキーに含める必要がある
まとめ
MongoDBを使用した無限スクロールの実装において、ソート挙動に起因する問題と遭遇した。
以下のケースで発生しうる。
- ソート対象フィールドに重複値が存在
- 大量データのページネーション処理のページ境界
- 複数のフィルター条件の組み合わせ
_id
フィールドを補助的なソートキーとして活用することで、安定したソート結果とクリーンな無限スクロールの実装が可能となる。