0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

MongoDBのソート挙動に起因するReactの無限スクロールでの重複キー問題の解決

Last updated at Posted at 2025-02-05

はじめに

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のソート挙動の特徴

  1. 同一のソート値を持つドキュメントが存在する場合、その順序は必ずしも一貫性を持たない
  2. ページネーション(skiplimit)と組み合わさると、同一ドキュメントが異なるページに出現する可能性がある
  3. これにより、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();

なぜ解決するのか

  1. _idフィールドはMongoDBにおいて一意性が保証されている
  2. 主ソートキーが同値の場合でも、_idによる順序付けで一貫性のある結果が得られる
  3. これにより、ページネーション時の重複が防止される

技術的考察

MongoDBのソート挙動

  • デフォルトでは、同一ソート値を持つドキュメントの順序は不定
  • 分散システムやシャーディングの場合、この挙動はより顕著になる
  • 安定したソートには必ず一意な値をソートキーに含める必要がある

まとめ

MongoDBを使用した無限スクロールの実装において、ソート挙動に起因する問題と遭遇した。
以下のケースで発生しうる。

  1. ソート対象フィールドに重複値が存在
  2. 大量データのページネーション処理のページ境界
  3. 複数のフィルター条件の組み合わせ

_idフィールドを補助的なソートキーとして活用することで、安定したソート結果とクリーンな無限スクロールの実装が可能となる。

参考文献

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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?