facebook/relay · error · Error
NormalizationEngine: Expected record `${parentID}` to exist.
Error message
NormalizationEngine: Expected record `${parentID}` to exist. What it means
After normalizing a response, Relay registers @defer/@stream placeholders and must locate each placeholder's parent record in the mutable record source (`source.get(parentID)`). If that record is missing, the placeholder refers to data that was never written — typically a `dataID` mismatch between the payload normalization and the placeholder's selector, or a record that got GC'd/not committed. Relay throws rather than silently orphaning the incremental work.
Source
Thrown at packages/relay-runtime/store/NormalizationEngine.js:607
for (let i = 0; i < placeholders.length; i++) {
const placeholder = placeholders[i];
const {label, path} = placeholder;
const pathKey = path.map(String).join('.');
const key = makeKey(label, pathKey);
this._placeholders.set(key, placeholder);
// Cache parent record for @stream concurrent modification detection
// and for @defer handle field replay
let parentID: string;
if (placeholder.kind === 'stream') {
parentID = placeholder.parentID;
} else {
parentID = placeholder.selector.dataID;
}
const parentRecord = source.get(parentID);
if (parentRecord == null) {
throw err(
'NormalizationEngine: Expected record `' + parentID + '` to exist.',
);
}
const parentPayloads = (fieldPayloads ?? []).filter(
(fieldPayload: HandleFieldPayload) => {
const fieldID = generateClientID(
fieldPayload.dataID,
fieldPayload.fieldKey,
);
return fieldPayload.dataID === parentID || fieldID === parentID;
},
);
const previousEntry = this._parentRecords.get(parentID);
if (previousEntry != null) {
const nextRecord = RelayModernRecord.update(
previousEntry.record,View on GitHub (pinned to 668b1b85e0)
Solutions
- Normalize the base response into the same record source before processing defer/stream followups
- Ensure the dataID used in the followup/defer selector matches the root/payload id actually normalized
- Check that garbage collection isn't running while multipart payloads are still in flight (retain queries)
- Log placeholder.parentID / selector.dataID and confirm source.get(dataID) exists before normalizing followups
Example fix
// before followupResponses.forEach(r => engine.normalize(r)); // source empty // after engine.normalize(baseResponse); // writes parent records followupResponses.forEach(r => engine.normalize(r));
Defensive patterns
Strategy: validation
Validate before calling
const parentID = placeholder.kind === 'stream' ? placeholder.parentID : placeholder.selector.dataID;
if (source.get(parentID) == null) {
throw new Error(`Parent record ${parentID} missing before registering placeholder; normalize base response first`);
} Prevention
- Process multipart payloads strictly in order against one record source
- Retain queries so GC doesn't evict parents mid-stream
- Keep dataIDs consistent between base and followup normalizations
When it happens
Trigger: Calling normalize (via processResponse) on a response containing @defer/@stream placeholders whose parent selector.dataID / parentID does not exist in the record source — e.g. normalizing a followup payload against an empty record source, a base response that was never normalized, or IDs that changed between base and followup normalizations.
Common situations: Custom environments normalizing incremental payloads with a fresh/blank NormalizationInboundTextStorage, tests invoking _processDefer/_normalizeFollowup out of order, or store eviction/GC removing a parent record mid-multipart-response.
Related errors
- NormalizationEngine: Expected record `${parentID}` to have f
- Did not find item with data id ${__id} in the store.
- RelayModernStore: Cannot batch updates while already batchin
- NormalizationEngine: Expected incremental response to have `
- NormalizationEngine: Expected id of elements of field `${sto
AI-assisted analysis of facebook/relay@668b1b85e0 (2026-09-02).
Data as JSON: /api/errors/aa696eb4cc80b7f6.
Report an issue: GitHub.