[Flight] Cyclic references inside Map/Set values are silently corrupted to null on the client

Abierto
#37,619 1 comentario 0 reacciones 0 asignados Ver en GitHub

Nadie ha tomado este issue todavía.

Evaluación

Dificultad
4/5
Tiempo estimado
3-5 días
Aptitud para principiantes
55/100
Tipo de issue
Error
Claridad
Bien especificado
Estado de actividad
Activo
Stack tecnológico
javascript, react
Área
full-stack

Línea de trabajo

Empieza en packages/react-server/src/ReactFlightServer.js leyendo serializeMap y serializeSet, y luego ejecuta la reproducción proporcionada de renderToPipeableStream/createFromNodeStream. Sigue el recorrido del cliente para el manejo de ciclos de las referencias hasta la fila de serialización de Map/Set. Se considera terminado cuando los tres autociclos reportados se resuelven en sus instancias reales de Map/Set en lugar de null.

Escrito por el modelo de indexación a partir del texto del issue.

Descripción

Description

Cyclic references inside Map/Set values are silently corrupted to null when serialized with React Server Components (Flight) and parsed on the client. Cyclic references in plain objects/arrays work fine (and are officially tested), so the Map/Set case is a data-corruption bug, not a "unsupported input" case.

Affected versions: tested on react-server-dom-webpack@19.3.0 (latest stable).

Reproduction

// server
import {renderToPipeableStream} from 'react-server-dom-webpack/server.node';
// client
import {createFromNodeStream} from 'react-server-dom-webpack/client';

// Case 1: Map value self-cycle
const m = new Map();
m.set('self', m);

// Case 2: Set self-cycle
const s = new Set();
s.add(s);

// Case 3: Set contains an object that references the Set back
const s2 = new Set();
const o = {back: s2};
s2.add(o);

const model = {m, s, s2};
// renderToPipeableStream(model, webpackMap) -> stream
// createFromNodeStream(stream, webpackMap)

Result (client side):

result.m.get('self') === null;   // expected: === m (the Map itself)
[...result.s][0] === null;       // expected: === s (the Set itself)
[...result.s2][0].back === null; // expected: === s2 (the Set itself)

Note: m.get('self') returns null, not m. No error is thrown — the cycle is silently lost.

Working control cases (for comparison)

The same shapes with plain objects and arrays round-trip correctly (React officially supports cyclic objects/arrays):

const cycObj = {name: 'x'};
cycObj.self = cycObj;        // ok, back: cycObj.self === cycObj
const cycArr = [1];
cycArr.push(cycArr);          // ok

// A Map whose value points to an independent (non-cyclic) object path also works:
const foo = {};
const set = new Set([foo]);
foo.bar = set;                // ok: set contains foo, foo.bar === set

The failing cases all have one thing in common: the cycle references back to the Map/Set's own serialization position (either the Map/Set is the root, or the reference goes through the property that holds the Map/Set, e.g. $0, $0:s, $0:m in the wire format).

Root cause

serializeMap/serializeSet (packages/react-server/src/ReactFlightServer.js) outline the entries into a separate chunk/row:

function serializeMap(request, map) {
  const entries = Array.from(map);
  const id = outlineModel(request, entries);
  return '$Q' + id.toString(16);
}

Wire format for {m: m} where m.set('self', m):

0:{"m":"$Q1"}
1:[["self","$0:m"]]

The client parses row 1 (the Map entries) while row 0 (the root) is still blocked waiting for row 1; row 1 references $0:m which requires row 0 to be initialized. This cycle is not broken by the client's cycle-handling logic, so the reference resolves to null. Plain objects/arrays are inlined into the parent row and go through the reviver/reify path, where cyclic references are handled correctly.

Related: #37542 fixed cyclic references for the case where the reference points to an independently-resolvable object (e.g. Set([foo]) + foo.bar = set + Promise-wrapped rows). The Map/Set cases above (references that close back onto the Map/Set's own row) are still broken after that fix.

Expected behavior

Cyclic references inside Map/Set should resolve to the actual Map/Set instance, consistent with cyclic plain objects/arrays. At minimum, a corrupted cycle should not silently become null.

Lenguaje dominante
JavaScript
Estrellas
251k
Forks
51.4k
Merge medio
2 d 1 h
PR fusionados (30 d)
51

Guía de contribución

Abrir la guía de contribución

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Más de react/react

Todos los issues de react/react

Issues similares

Más issues de JavaScript

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.