CodeSampleX

Beispiel

react-dom 19.2.8: Hydrate SSR HTML with hydrateRoot, handle hydration mismatch recovery, and configure React 19 onCaughtError, onUncaughtError, and onRecoverableError callbacks

Verifiziertes Beispiel für npm react-dom 19.2.8: Hydrate SSR HTML with hydrateRoot, handle hydration mismatch recovery, and configure React 19 onCaughtError…

sha256:5436cd1a4056e1c48d4f46d5a70e484e7f9126365b1903606810b7178e550399

Dieses Netzwerk bietet eine Sache: ein Sample, das baut. Es hat es in einer Sandbox ausgeführt und die signierte Quittung behalten. Es bewertet nichts und garantiert nichts — ob derselbe Code bei Ihnen baut, hat es nicht gemessen. Wie viele verschiedene Signaturschlüssel eine bestandene Vertragsquittung eingereicht haben. Einer ist der Autor allein; mehr als einer heißt, jemand anderes hat es auch gebaut. Ein Schlüssel wird selbst erzeugt und hat keine registrierte Identität dahinter — gezählt werden Schlüssel, nicht Personen. MIT-0

Ausführungsbelege

Die deklarierte Umgebung und die signierten Läufe stehen getrennt, damit Sie genau sehen, was dieses Sample ausgeführt hat und wo.

Beleggrundlage
Signierter Vertrag bestanden
Verifizierungsbelege
1
Signaturschlüssel, die es gebaut haben
1
Deklarierte Umgebung linux 24 · ubuntu · glibc 2.39 x64 npm

Umgebungen der Verifizierungsläufe

Umgebung Contract Stufen Lauf
node 22 · linux alpine/x64 · docker ed25519:c1973797be207ac4 PASS compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS
CONTAINER_RUN · node-typescript@1node:22-alpine@sha256:c610fcdfb1d5…
2026-08-26

Fall

HOW
Ziel
Hydrate SSR HTML with hydrateRoot, handle hydration mismatch recovery, and configure React 19 onCaughtError, onUncaughtError, and onRecoverableError callbacks
Pakete
Symbole
  • hydrateRoot
  • react-dom/client.hydrateRoot
Erstellt
2026-08-26T08:08:50Z

Contract

  1. hydrateRoot is exported from react-dom/client and is absent from the react-dom root entrypoint
  2. clean SSR hydration attaches to server-rendered DOM without recoverable errors
  3. hydration mismatch invokes onRecoverableError with mismatch details and recovers by regenerating client DOM
  4. component errors caught by Error Boundary trigger root onCaughtError with error details and componentStack
  5. unhandled component errors during hydration trigger root onUncaughtError with componentStack
  6. root.unmount synchronously clears host DOM and invalidates the root against subsequent renders

Dateien

  • NOTES.md
  • PROMPT.md
  • csx.json
  • package-lock.json
  • package.json
  • spec.json
  • src/index.mjs
  • test/contract.mjs

Quellartefakt herunterladen (tar.gz)

Quelltext

NOTES.md
# React DOM client hydration and React 19 error handling

## Prior CSX search

`search_known_solution` was queried for `pkg:npm/react-dom@19.2.8` and `pkg:npm/react@19.2.8` covering `hydrateRoot` and React 19 root error callbacks (`onCaughtError`, `onUncaughtError`, `onRecoverableError`). No existing verified sample matched, returning `NO_SAFE_MATCH`.

## The tempting wrong model

Callers commonly assume:
1. `hydrateRoot` is exported directly from the `react-dom` package root.
2. Hydration mismatches between SSR markup and client React elements throw synchronous fatal errors or crash the root.
3. Component rendering errors during hydration only interact with `componentDidCatch` without notifying root-level error telemetry.
4. An unmounted root can be updated by calling `root.render()` again.

## Observable failure

1. `react-dom.hydrateRoot` is `undefined`; the function is exported exclusively from `react-dom/client`.
2. In React 19, hydration mismatches invoke the `onRecoverableError` callback with structured diff details and automatically regenerate the mismatched subtree on the client without crashing.
3. Errors caught by an `ErrorBoundary` trigger `options.onCaughtError` with the thrown error and `errorInfo.componentStack`.
4. Unhandled errors during hydration trigger `options.onUncaughtError` with `componentStack`.
5. Calling `root.render()` on an unmounted root throws `Cannot update an unmounted root`.
PROMPT.md
Clean-room public code sample — generation instructions

Write a brand-new, minimal, self-contained code sample in this clean-room directory.
Do not copy, paraphrase, or reference any existing project source. Work only from this spec.

A csx.json manifest scaffold already exists. Do not recreate it from memory. Preserve its case.goal, packages and symbols; fill its empty case.contract with exact assertions and correct its environment, commands and verifierAdapter for the files you generate.

Goal: Hydrate SSR HTML with hydrateRoot, handle hydration mismatch recovery, and configure React 19 onCaughtError, onUncaughtError, and onRecoverableError callbacks
Kind: HOW

Use EXACTLY these public packages and versions:
  - pkg:npm/react-dom@19.2.8
  - pkg:npm/react@19.2.8
Demonstrate these symbols/APIs:
  - hydrateRoot
  - react-dom/client.hydrateRoot

Rules:
  - One focused purpose; the smallest project that proves the goal.
  - Include a contract test (test/contract.*) that runs OFFLINE and exits 0 exactly when the goal behavior works.
  - Pin every dependency with a lockfile so resolution is reproducible.
  - No secrets, credentials, or tokens. No real URLs (only example.com or localhost). No absolute paths.
  - No personal names, emails, company names, or project identifiers of any kind.
  - No binaries and no generated output (node_modules, dist, target, venv, .git, .env).
  - Keep it under 200 files and 256KB packed.
csx.json
{"case":{"caseId":"case:sha256:4f5cbe93bb3c9b157eeed68aba57564df1a558ef8e624985ad50c6d4a284f426","contract":["hydrateRoot is exported from react-dom/client and is absent from the react-dom root entrypoint","clean SSR hydration attaches to server-rendered DOM without recoverable errors","hydration mismatch invokes onRecoverableError with mismatch details and recovers by regenerating client DOM","component errors caught by Error Boundary trigger root onCaughtError with error details and componentStack","unhandled component errors during hydration trigger root onUncaughtError with componentStack","root.unmount synchronously clears host DOM and invalidates the root against subsequent renders"],"goal":"Hydrate SSR HTML with hydrateRoot, handle hydration mismatch recovery, and configure React 19 onCaughtError, onUncaughtError, and onRecoverableError callbacks","kind":"HOW","packages":["pkg:npm/react-dom@19.2.8","pkg:npm/react@19.2.8"],"schemaVersion":1,"symbols":["hydrateRoot","react-dom/client.hydrateRoot"]},"contractCommand":["node","test/contract.mjs"],"environment":{"arch":"x64","distro":"ubuntu","ecosystem":"npm","libc":"glibc","libcVersion":"2.39","os":"linux","osVersionBucket":"24","packageManager":"npm","schemaVersion":1},"license":"MIT-0","packages":["pkg:npm/react-dom@19.2.8","pkg:npm/react@19.2.8"],"schemaVersion":1,"symbols":["hydrateRoot","react-dom/client.hydrateRoot"],"verifierAdapter":"node-typescript@1"}
package-lock.json
{
  "name": "csx-sample-react-dom-hydrate-root",
  "version": "1.0.0",
  "lockfileVersion": 3,
  "requires": true,
  "packages": {
    "": {
      "name": "csx-sample-react-dom-hydrate-root",
      "version": "1.0.0",
      "dependencies": {
        "react": "19.2.8",
        "react-dom": "19.2.8"
      },
      "devDependencies": {
        "jsdom": "29.1.1"
      }
    },
    "node_modules/@asamuzakjp/css-color": {
      "version": "5.1.11",
      "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-5.1.11.tgz",
      "integrity": "sha512-KVw6qIiCTUQhByfTd78h2yD1/00waTmm9uy/R7Ck/ctUyAPj+AEDLkQIdJW0T8+qGgj3j5bpNKK7Q3G+LedJWg==",
      "dev": true,
      "license": "MIT",
      "dependencies": {
        "@asamuzakjp/generational-cache": "^1.0.1",
        "@csstools/css-calc": "^3.2.0",
        "@csstools/css-color-parser": "^4.1.0",
        "@csstools/css-parser-algorithms": "^4.0.0",
        "@csstools/css-tokenizer": "^4.0.0"
      },
      "engines": {
        "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
      }
    },
    "node_modules/@asamuzakjp/dom-selector": {
      "version": "7.1.1",
      "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-7.1.1.tgz",
      "integrity": "sha512-67RZDnYRc8H/8MLDgQCDE//zoqVFwajkepHZgmXrbwybzXOEwOWGPYGmALYl9J2DOLfFPPs6kKCqmbzV895hTQ==",
      "dev": true,
      "license": "MIT",
      "dependencies": {
        "@asamuzakjp/generational-cache": "^1.0.1",
        "@asamuzakjp/nwsapi": "^2.3.9",
        "bidi-js": "^1.0.3",
        "css-tree": "^3.2.1",
        "is-potential-custom-element-name": "^1.0.1"
      },
      "engines": {
        "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
      }
    },
    "node_modules/@asamuzakjp/generational-cache": {
      "version": "1.0.1",
      "resolved": "https://registry.npmjs.org/@asamuzakjp/generational-cache/-/generational-cache-1.0.1.tgz",
      "integrity": "sha512-wajfB8KqzMCN2KGNFdLkReeHncd0AslUSrvHVvvYWuU8ghncRJoA50kT3zP9MVL0+9g4/67H+cdvBskj9THPzg==",
      "dev": true,
      "license": "MIT",
      "engines": {
        "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
      }
    },
    "node_modules/@asamuzakjp/nwsapi": {
      "version": "2.3.9",
      "resolved": "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz",
      "integrity": "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==",
      "dev": true,
      "license": "MIT"
    },
    "node_modules/@bramus/specificity": {
      "version": "2.4.2",
      "resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz",
      "integrity": "sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==",
      "dev": true,
      "license": "MIT",
      "dependencies": {
        "css-tree": "^3.0.0"
      },
      "bin": {
        "specificity": "bin/cli.js"
      }
    },
    "node_modules/@csstools/color-helpers": {
      "version": "6.1.1",
      "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.1.1.tgz",
      "integrity": "sha512-gLNsunvwf3mCi5u5o46/Z/JcJMnhbHSaZ69rkgPzNM3J4s8hWwpPUQB6/tt0EDFyCiWzxANlx+2LJwpYj4zS1w==",
      "dev": true,
      "funding": [
        {
          "type": "github",
          "url": "https://github.com/sponsors/csstools"
        },
        {
          "type": "opencollective",
          "url": "https://opencollective.com/csstools"
        }
      ],
      "license": "MIT-0",
      "engines": {
        "node": ">=20.19.0"
      }
    },
    "node_modules/@csstools/css-calc": {
      "version": "3.3.0",
      "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.3.0.tgz",
      "integrity": "sha512-c5ihYsPkdG6JCkU2zTMm4+k6r7RXuGxtWYhu5DHMIiF1FHzrfmHL5so11AoFpUv/tu61xfcmT4AmKoFfMPoqdQ==",
      "dev": true,
      "funding": [
        {
          "type": "github",
          "url": "https://github.com/sponsors/csstools"
        },
        {
          "type": "opencollective",
          "url": "https://opencollective.com/csstools"
        }
      ],
      "license": "MIT",
      "engines": {
        "node": ">=20.19.0"
      },
      "peerDependencies": {
        "@csstools/css-parser-algorithms": "^4.0.0",
        "@csstools/css-tokenizer": "^4.0.0"
      }
    },
    "node_modules/@csstools/css-color-parser": {
      "version": "4.2.1",
      "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.2.1.tgz",
      "integrity": "sha512-YpAJZhaHplYQkG8ib+/Fx5Y0eF2lVWi3tIvMJA6i39TLyUNp2439cifzW8VMjhlqrBjHzK5hVGugRRm2zTKI/A==",
      "dev": true,
      "funding": [
        {
          "type": "github",
          "url": "https://github.com/sponsors/csstools"
        },
        {
          "type": "opencollective",
          "url": "https://opencollective.com/csstools"
        }
      ],
      "license": "MIT",
      "dependencies": {
        "@csstools/color-helpers": "^6.1.1",
        "@csstools/css-calc": "^3.3.0"
      },
      "engines": {
        "node": ">=20.19.0"
      },
      "peerDependencies": {
        "@csstools/css-parser-algorithms": "^4.0.0",
        "@csstools/css-tokenizer": "^4.0.0"
      }
    },
    "node_modules/@csstools/css-parser-algorithms": {
      "version": "4.0.0",
      "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.0.tgz",
      "integrity": "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==",
      "dev": true,
      "funding": [
        {
          "type": "github",
          "url": "https://github.com/sponsors/csstools"
        },
        {
          "type": "opencollective",
          "url": "https://opencollective.com/csstools"
        }
      ],
      "license": "MIT",
      "engines": {
        "node": ">=20.19.0"
      },
      "peerDependencies": {
        "@csstools/css-tokenizer": "^4.0.0"
      }
    },
    "node_modules/@csstools/css-syntax-patches-for-csstree": {
      "version": "1.1.9",
      "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.9.tgz",
      "integrity": "sha512-iGGw4OsAYsS6pD29MdJ2bX/nJx65a04ZZiw6x+VwWlP2DdXf6f++Zmuv/OzALpdyfVhjbduIIF2cXM7HWBIe9A==",
      "dev": true,
      "funding": [
        {
          "type": "github",
          "url": "https://github.com/sponsors/csstools"
        },
        {
          "type": "opencollective",
          "url": "https://opencollective.com/csstools"
        }
      ],
      "license": "MIT-0",
      "peerDependencies": {
        "css-tree": "^3.2.1"
      },
      "peerDependenciesMeta": {
        "css-tree": {
          "optional": true
        }
      }
    },
    "node_modules/@csstools/css-tokenizer": {
      "version": "4.0.0",
      "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz",
      "integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==",
      "dev": true,
      "funding": [
        {
          "type": "github",
          "url": "https://github.com/sponsors/csstools"
        },
        {
          "type": "opencollective",
          "url": "https://opencollective.com/csstools"
        }
      ],
      "license": "MIT",
      "engines": {
        "node": ">=20.19.0"
      }
    },
    "node_modules/@exodus/bytes": {
      "version": "1.15.1",
      "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.15.1.tgz",
      "integrity": "sha512-S6mL0yNB/Abt9Ei4tq8gDhcczc4S3+vQ4ra7vxnAf+YHC02srtqxKKZghx2Dq6p0e66THKwR6r8N6P95wEty7Q==",
      "dev": true,
      "license": "MIT",
      "engines": {
        "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
      },
      "peerDependencies": {
        "@noble/hashes": "^1.8.0 || ^2.0.0"
      },
      "peerDependenciesMeta": {
        "@noble/hashes": {
          "optional": true
        }
      }
    },
    "node_modules/bidi-js": {
      "version": "1.0.3",
      "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz",
      "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==",
      "dev": true,
      "license": "MIT",
      "dependencies": {
        "require-from-string": "^2.0.2"
      }
    },
    "node_modules/css-tree": {
      "version": "3.2.1",
      "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz",
      "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==",
      "dev": true,
      "license": "MIT",
      "dependencies": {
        "mdn-data": "2.27.1",
        "source-map-js": "^1.2.1"
      },
      "engines": {
        "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0"
      }
    },
    "node_modules/data-urls": {
      "version": "7.0.0",
      "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz",
      "integrity": "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==",
      "dev": true,
      "license": "MIT",
      "dependencies": {
        "whatwg-mimetype": "^5.0.0",
        "whatwg-url": "^16.0.0"
      },
      "engines": {
        "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
      }
    },
    "node_modules/decimal.js": {
      "version": "10.6.0",
      "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz",
      "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==",
      "dev": true,
      "license": "MIT"
    },
    "node_modules/entities": {
      "version": "8.0.0",
      "resolved": "https://registry.npmjs.org/entities/-/entities-8.0.0.tgz",
      "integrity": "sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==",
      "dev": true,
      "license": "BSD-2-Clause",
      "engines": {
        "node": ">=20.19.0"
      },
      "funding": {
        "url": "https://github.com/fb55/entities?sponsor=1"
      }
    },
    "node_modules/html-encoding-sniffer": {
      "version": "6.0.0",
      "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz",
      "integrity": "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==",
      "dev": true,
      "license": "MIT",
      "dependencies": {
        "@exodus/bytes": "^1.6.0"
      },
      "engines": {
        "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
      }
    },
    "node_modules/is-potential-custom-element-name": {
      "version": "1.0.1",
      "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz",
      "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==",
      "dev": true,
      "license": "MIT"
    },
    "node_modules/jsdom": {
      "version": "29.1.1",
      "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-29.1.1.tgz",
      "integrity": "sha512-ECi4Fi2f7BdJtUKTflYRTiaMxIB0O6zfR1fX0GXpUrf6flp8QIYn1UT20YQqdSOfk2dfkCwS8LAFoJDEppNK5Q==",
      "dev": true,
      "license": "MIT",
      "dependencies": {
        "@asamuzakjp/css-color": "^5.1.11",
        "@asamuzakjp/dom-selector": "^7.1.1",
        "@bramus/specificity": "^2.4.2",
        "@csstools/css-syntax-patches-for-csstree": "^1.1.3",
        "@exodus/bytes": "^1.15.0",
        "css-tree": "^3.2.1",
        "data-urls": "^7.0.0",
        "decimal.js": "^10.6.0",
        "html-encoding-sniffer": "^6.0.0",
        "is-potential-custom-element-name": "^1.0.1",
        "lru-cache": "^11.3.5",
        "parse5": "^8.0.1",
        "saxes": "^6.0.0",
        "symbol-tree": "^3.2.4",
        "tough-cookie": "^6.0.1",
        "undici": "^7.25.0",
        "w3c-xmlserializer": "^5.0.0",
        "webidl-conversions": "^8.0.1",
        "whatwg-mimetype": "^5.0.0",
        "whatwg-url": "^16.0.1",
        "xml-name-validator": "^5.0.0"
      },
      "engines": {
        "node": "^20.19.0 || ^22.13.0 || >=24.0.0"
      },
      "peerDependencies": {
        "canvas": "^3.0.0"
      },
      "peerDependenciesMeta": {
        "canvas": {
          "optional": true
        }
      }
    },
    "node_modules/lru-cache": {
      "version": "11.5.2",
      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz",
      "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==",
      "dev": true,
      "license": "BlueOak-1.0.0",
      "engines": {
        "node": "20 || >=22"
      }
    },
    "node_modules/mdn-data": {
      "version": "2.27.1",
      "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz",
      "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==",
      "dev": true,
      "license": "CC0-1.0"
    },
    "node_modules/parse5": {
      "version": "8.0.1",
      "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.1.tgz",
      "integrity": "sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==",
      "dev": true,
      "license": "MIT",
      "dependencies": {
        "entities": "^8.0.0"
      },
      "funding": {
        "url": "https://github.com/inikulin/parse5?sponsor=1"
      }
    },
    "node_modules/punycode": {
      "version": "2.3.1",
      "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
      "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
      "dev": true,
      "license": "MIT",
      "engines": {
        "node": ">=6"
      }
    },
    "node_modules/react": {
      "version": "19.2.8",
      "resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz",
      "integrity": "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==",
      "license": "MIT",
      "engines": {
        "node": ">=0.10.0"
      }
    },
    "node_modules/react-dom": {
      "version": "19.2.8",
      "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.8.tgz",
      "integrity": "sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==",
      "license": "MIT",
      "dependencies": {
        "scheduler": "^0.27.0"
      },
      "peerDependencies": {
        "react": "^19.2.8"
      }
    },
    "node_modules/require-from-string": {
      "version": "2.0.2",
      "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
      "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
      "dev": true,
      "license": "MIT",
      "engines": {
        "node": ">=0.10.0"
      }
    },
    "node_modules/saxes": {
      "version": "6.0.0",
      "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz",
      "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==",
      "dev": true,
      "license": "ISC",
      "dependencies": {
        "xmlchars": "^2.2.0"
      },
      "engines": {
        "node": ">=v12.22.7"
      }
    },
    "node_modules/scheduler": {
      "version": "0.27.0",
      "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
      "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
      "license": "MIT"
    },
    "node_modules/source-map-js": {
      "version": "1.2.1",
      "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
      "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
      "dev": true,
      "license": "BSD-3-Clause",
      "engines": {
        "node": ">=0.10.0"
      }
    },
    "node_modules/symbol-tree": {
      "version": "3.2.4",
      "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
      "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==",
      "dev": true,
      "license": "MIT"
    },
    "node_modules/tldts": {
      "version": "7.4.11",
      "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.4.11.tgz",
      "integrity": "sha512-aBiNayCfTQxuIJBm06M+xR14cYaYlDlSXZbgsnKzKNxDKUVq7KFwTjwBSsb7m9Y5xO8WfPnBc63WaYFMTGlvqw==",
      "dev": true,
      "license": "MIT",
      "dependencies": {
        "tldts-core": "^7.4.11"
      },
      "bin": {
        "tldts": "bin/cli.js"
      }
    },
    "node_modules/tldts-core": {
      "version": "7.4.11",
      "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.4.11.tgz",
      "integrity": "sha512-CW3WN2rIIE/Of21mulhgnGOwoDyEFNygyIBOONSdyAuSATgMMUCpLeUlB+E8sAwA5xRV9hYPl+kyZ9citHCaKg==",
      "dev": true,
      "license": "MIT"
    },
    "node_modules/tough-cookie": {
      "version": "6.0.2",
      "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.2.tgz",
      "integrity": "sha512-exgYmnmL/sJpR3upZfXG5PoatXQii55xAiXGXzY+sROLZ/Y+SLcp9PgJNI9Vz37HpQ74WvDcLT8eqm+kV3FzrA==",
      "dev": true,
      "license": "BSD-3-Clause",
      "dependencies": {
        "tldts": "^7.0.5"
      },
      "engines": {
        "node": ">=16"
      }
    },
    "node_modules/tr46": {
      "version": "6.0.0",
      "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz",
      "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==",
      "dev": true,
      "license": "MIT",
      "dependencies": {
        "punycode": "^2.3.1"
      },
      "engines": {
        "node": ">=20"
      }
    },
    "node_modules/undici": {
      "version": "7.29.0",
      "resolved": "https://registry.npmjs.org/undici/-/undici-7.29.0.tgz",
      "integrity": "sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw==",
      "dev": true,
      "license": "MIT",
      "engines": {
        "node": ">=20.18.1"
      }
    },
    "node_modules/w3c-xmlserializer": {
      "version": "5.0.0",
      "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz",
      "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==",
      "dev": true,
      "license": "MIT",
      "dependencies": {
        "xml-name-validator": "^5.0.0"
      },
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/webidl-conversions": {
      "version": "8.0.1",
      "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz",
      "integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==",
      "dev": true,
      "license": "BSD-2-Clause",
      "engines": {
        "node": ">=20"
      }
    },
    "node_modules/whatwg-mimetype": {
      "version": "5.0.0",
      "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz",
      "integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==",
      "dev": true,
      "license": "MIT",
      "engines": {
        "node": ">=20"
      }
    },
    "node_modules/whatwg-url": {
      "version": "16.0.1",
      "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.1.tgz",
      "integrity": "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==",
      "dev": true,
      "license": "MIT",
      "dependencies": {
        "@exodus/bytes": "^1.11.0",
        "tr46": "^6.0.0",
        "webidl-conversions": "^8.0.1"
      },
      "engines": {
        "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
      }
    },
    "node_modules/xml-name-validator": {
      "version": "5.0.0",
      "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz",
      "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==",
      "dev": true,
      "license": "Apache-2.0",
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/xmlchars": {
      "version": "2.2.0",
      "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz",
      "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
      "dev": true,
      "license": "MIT"
    }
  }
}
package.json
{
  "name": "csx-sample-react-dom-hydrate-root",
  "version": "1.0.0",
  "private": true,
  "type": "module",
  "license": "MIT-0",
  "scripts": {
    "test": "node test/contract.mjs"
  },
  "dependencies": {
    "react": "19.2.8",
    "react-dom": "19.2.8"
  },
  "devDependencies": {
    "jsdom": "29.1.1"
  }
}
spec.json
{
  "schemaVersion": 1,
  "goal": "Hydrate SSR HTML with hydrateRoot, handle hydration mismatch recovery, and configure React 19 onCaughtError, onUncaughtError, and onRecoverableError callbacks",
  "kind": "HOW",
  "packages": [
    "pkg:npm/react-dom@19.2.8",
    "pkg:npm/react@19.2.8"
  ],
  "symbols": [
    "hydrateRoot",
    "react-dom/client.hydrateRoot"
  ]
}
src/index.mjs
import { flushSync } from 'react-dom';
import { hydrateRoot } from 'react-dom/client';

/**
 * Hydrate a server-rendered DOM container with a React 19 client tree,
 * synchronously flushing initial hydration and binding root error callbacks.
 */
export function hydrateRootWithCallbacks(container, element, options = {}) {
  const {
    onRecoverableError,
    onCaughtError,
    onUncaughtError,
    identifierPrefix,
  } = options;

  let root;
  flushSync(() => {
    root = hydrateRoot(container, element, {
      onRecoverableError,
      onCaughtError,
      onUncaughtError,
      identifierPrefix,
    });
  });

  return root;
}
test/contract.mjs
import assert from 'node:assert/strict';
import { JSDOM } from 'jsdom';

const dom = new JSDOM('<!doctype html><html><body></body></html>', {
  url: 'https://example.test/',
});

const priorGlobals = new Map();
for (const name of ['window', 'document']) {
  priorGlobals.set(name, Object.getOwnPropertyDescriptor(globalThis, name));
}

Object.defineProperty(globalThis, 'window', {
  configurable: true,
  writable: true,
  value: dom.window,
});
Object.defineProperty(globalThis, 'document', {
  configurable: true,
  writable: true,
  value: dom.window.document,
});

try {
  const React = await import('react');
  const { createElement: h, Component } = React.default || React;
  const ReactDOM = await import('react-dom');
  const ReactDOMClient = await import('react-dom/client');
  const { renderToString } = await import('react-dom/server');
  const { hydrateRootWithCallbacks } = await import('../src/index.mjs');

  // Contract 1: hydrateRoot is exported from react-dom/client and is absent from the react-dom root entrypoint
  assert.equal(typeof ReactDOMClient.hydrateRoot, 'function');
  assert.equal(ReactDOM.hydrateRoot, undefined);

  // Contract 2: clean SSR hydration attaches to server-rendered DOM without recoverable errors
  function CounterApp({ count }) {
    return h('div', { id: 'counter-app' }, h('button', { id: 'btn' }, `Count: ${count}`));
  }

  const cleanContainer = document.createElement('div');
  cleanContainer.innerHTML = renderToString(h(CounterApp, { count: 0 }));
  document.body.appendChild(cleanContainer);

  let cleanRecoverableErrors = [];
  const rootClean = hydrateRootWithCallbacks(
    cleanContainer,
    h(CounterApp, { count: 0 }),
    {
      onRecoverableError(error, errorInfo) {
        cleanRecoverableErrors.push({ error, errorInfo });
      },
    },
  );

  assert.equal(cleanRecoverableErrors.length, 0);
  assert.equal(cleanContainer.querySelector('#btn').textContent, 'Count: 0');
  rootClean.unmount();
  cleanContainer.remove();

  // Contract 3: hydration mismatch invokes onRecoverableError with mismatch details and recovers by regenerating client DOM
  const mismatchContainer = document.createElement('div');
  mismatchContainer.innerHTML = '<div id="mismatch"><p id="mismatch-msg">Server Content</p></div>';
  document.body.appendChild(mismatchContainer);

  let recoverableErrors = [];
  const rootMismatch = hydrateRootWithCallbacks(
    mismatchContainer,
    h('div', { id: 'mismatch' }, h('p', { id: 'mismatch-msg' }, 'Client Content')),
    {
      onRecoverableError(error, errorInfo) {
        recoverableErrors.push({ error, errorInfo });
      },
    },
  );

  assert.ok(recoverableErrors.length >= 1, 'Mismatch should invoke onRecoverableError');
  assert.ok(
    recoverableErrors.some(e => /hydration/i.test(e.error.message) || /server/i.test(e.error.message)),
    'Error message should describe hydration mismatch',
  );
  assert.equal(mismatchContainer.querySelector('#mismatch-msg').textContent, 'Client Content');
  rootMismatch.unmount();
  mismatchContainer.remove();

  // Contract 4: component errors caught by Error Boundary trigger root onCaughtError with error details and componentStack
  class ErrorBoundary extends Component {
    constructor(props) {
      super(props);
      this.state = { hasError: false };
    }
    static getDerivedStateFromError() {
      return { hasError: true };
    }
    render() {
      if (this.state.hasError) {
        return h('div', { id: 'fallback' }, 'Fallback Rendered');
      }
      return this.props.children;
    }
  }

  function CrashComponent({ message }) {
    throw new Error(message);
  }

  const errorContainer = document.createElement('div');
  errorContainer.innerHTML = '<div id="fallback">Initial Server</div>';
  document.body.appendChild(errorContainer);

  let caughtErrors = [];
  const rootCaught = hydrateRootWithCallbacks(
    errorContainer,
    h(ErrorBoundary, null, h(CrashComponent, { message: 'Boundary caught this crash' })),
    {
      onCaughtError(error, errorInfo) {
        caughtErrors.push({ error, errorInfo });
      },
    },
  );

  assert.equal(caughtErrors.length, 1);
  assert.equal(caughtErrors[0].error.message, 'Boundary caught this crash');
  assert.ok(typeof caughtErrors[0].errorInfo.componentStack === 'string');
  assert.equal(errorContainer.querySelector('#fallback').textContent, 'Fallback Rendered');
  rootCaught.unmount();
  errorContainer.remove();

  // Contract 5: unhandled component errors during hydration trigger root onUncaughtError with componentStack
  const uncaughtContainer = document.createElement('div');
  uncaughtContainer.innerHTML = '<div id="uncaught">Initial Server</div>';
  document.body.appendChild(uncaughtContainer);

  let uncaughtErrors = [];
  const rootUncaught = hydrateRootWithCallbacks(
    uncaughtContainer,
    h(CrashComponent, { message: 'Uncaught hydration failure' }),
    {
      onUncaughtError(error, errorInfo) {
        uncaughtErrors.push({ error, errorInfo });
      },
    },
  );

  assert.equal(uncaughtErrors.length, 1);
  assert.equal(uncaughtErrors[0].error.message, 'Uncaught hydration failure');
  assert.ok(typeof uncaughtErrors[0].errorInfo.componentStack === 'string');
  rootUncaught.unmount();
  uncaughtContainer.remove();

  // Contract 6: root.unmount synchronously clears host DOM and invalidates the root against subsequent renders
  const lifecycleContainer = document.createElement('div');
  lifecycleContainer.innerHTML = '<div><span id="target">Content</span></div>';
  document.body.appendChild(lifecycleContainer);

  const rootLifecycle = hydrateRootWithCallbacks(
    lifecycleContainer,
    h('div', null, h('span', { id: 'target' }, 'Content')),
  );

  assert.equal(lifecycleContainer.querySelector('#target').textContent, 'Content');
  rootLifecycle.unmount();
  assert.equal(lifecycleContainer.innerHTML, '');

  assert.throws(
    () => rootLifecycle.render(h('p', null, 'Invalid update')),
    /Cannot update an unmounted root/,
  );
  lifecycleContainer.remove();

  console.log('CONTRACT PASS: React 19 hydrateRoot and error callbacks verified');
} finally {
  await new Promise((resolve) => setImmediate(resolve));
  dom.window.close();
  for (const [name, descriptor] of priorGlobals) {
    if (descriptor) {
      Object.defineProperty(globalThis, name, descriptor);
    } else {
      delete globalThis[name];
    }
  }
}

Ursprungs-Seeder

anonym