Bug: `FragmentInstance.compareDocumentPosition` alternates between `FOLLOWING` and `IMPLEMENTATION_SPECIFIC` on every other render

オープン
#37,610 コメント 3 件 リアクション 0 件 担当者 0 名 GitHub で見る

まだ誰も着手していません。

評価

難易度
3/5
見積もり時間
1〜2日
初心者へのやさしさ
76/100
issue の種類
バグ
明瞭さ
明確に書かれている
活発さ
活発
技術スタック
javascript, react
領域
frontend

調査の方向性

提供されたJSXの再現コードをReact 19.3.0で実行し、再レンダー間で位置マスクが交互になることを確認してください。次に packages/react-reconciler/src/ReactFiberTreeReflection.js を調査し、特に validateDocumentPositionWithFiberTree、isFiberPrecedingCheck、isFiberFollowingCheck を確認してください。再レンダー間で FOLLOWING と PRECEDING の結果が安定し、文書化されている空のケースやポータルのケースを変更しなければ完了です。

索引モデルが issue の本文から書いたものです。

説明

Status: Unconfirmed

[!NOTE]

AI Disclosure: An AI (Claude Fable 5.1) wrote the issue below. I checked it and take responsibility for it.

React version: 19.3.0 (react and react-dom). The relevant validation code
looks unchanged on current main, so I believe it is still affected.

Steps To Reproduce

  1. Render a <Fragment ref={fragmentRef}> with at least one DOM child, plus a
    sibling element after the fragment under the same parent.
  2. Call fragmentRef.current.compareDocumentPosition(afterSibling) and then
    trigger any state update (re-render).
  3. Repeat step 2.
import { Fragment, useRef, useState } from 'react';

function App() {
  const fragmentRef = useRef(null);
  const [results, setResults] = useState([]);
  return (
    <div>
      <button
        onClick={() => {
          const mask = fragmentRef.current.compareDocumentPosition(
            document.getElementById('after'),
          );
          // The state update re-renders, which flips the result of the
          // *next* click.
          setResults((prev) => [...prev, mask]);
        }}
      >
        compareDocumentPosition(afterSibling)
      </button>
      <Fragment ref={fragmentRef}>
        <p>fragment child</p>
      </Fragment>
      <p id="after">sibling after the fragment</p>
      <pre>{results.join(', ')}</pre>
    </div>
  );
}

Link to code example: https://github.com/uhyo/react-193-playground/blob/51307a38a34202644611223a3f73e415b5ae1640/docs/repro-compare-document-position.html

The current behavior

The result alternates with render parity:

4, 32, 4, 32, 4, 32

i.e. DOCUMENT_POSITION_FOLLOWING on even render generations and bare
DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC on odd ones. The
PRECEDING case (a sibling before the fragment) is affected the same way on
odd generations. Queries for nodes inside the fragment (CONTAINED_BY) are
stable.

Reproduced in Chromium with react-dom 19.3.0, in both development and
production builds, with and without StrictMode.

The expected behavior

A stable DOCUMENT_POSITION_FOLLOWING (4) / DOCUMENT_POSITION_PRECEDING (2)
regardless of how many times the tree has re-rendered. Per the
Fragment docs,
DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC is expected for empty
Fragments and Fragments with children rendered through a portal — neither
applies here.

Appendix

Probable cause

The fiber-tree validation added in #34069
(validateDocumentPositionWithFiberTree) returns
DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC when it cannot corroborate the
DOM-derived answer. Its helpers isFiberPrecedingCheck and
isFiberFollowingCheck in
packages/react-reconciler/src/ReactFiberTreeReflection.js compare fibers by
strict identity (child === boundary, child === target), while nearby
checks in the same file accept alternates (e.g.
current === fragmentFiber || current.alternate === fragmentFiber in the
CONTAINED_BY branch — which is presumably why that case is stable).

The traversal walks the current tree from the common ancestor, but target
comes from getClosestInstanceFromNode(otherNode) (the fiber cached on the
DOM node) and boundary from traversing the FragmentInstance's stored
_fragmentFiber — after a commit these can belong to the alternate
generation, so the identity comparisons miss, the search finds nothing,
validation reports failure, and the fallback 32 is returned. The
fiber/alternate pair swaps roles on each commit, which matches the observed
every-other-render alternation exactly.

Related work (not duplicates)

Searched existing issues/PRs mentioning compareDocumentPosition and
IMPLEMENTATION_SPECIFIC; none report this alternating behavior:

  • #32722 added compareDocumentPosition to fragment instances; #34069 added
    the fiber-tree validation this report concerns.
  • #37142 fixes an adjacent validation bug (CONTAINED_BY accepted DOM that
    was imperatively moved outside the fiber subtree) by switching to
    doesFiberContain, which does handle alternates — the preceding/following
    helpers may want the same treatment.
  • #37578/#37579 (compareDocumentPosition(document) TypeError),
    #37162/#37163 (empty fragments / empty portals), and #37606/#37607
    (portaled fragments in ShadowRoot/DocumentFragment) are different symptoms
    in the same API.
主要言語
JavaScript
スター
251k
フォーク
51.4k
平均マージ
2日 1時間
マージ済み PR(30日)
51

コントリビューションガイド

コントリビューションガイドを開く

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

react/react のほかの issue

react/react の issue をすべて見る

似ている issue

JavaScript の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。