Origin: objectui#6592's census (systematic sweep of every useEffect whose dependency array names a useMemo-bound identifier, cross-checked against a fetch-call-site heuristic, positive-controlled against the known packages/plugin-map member). That card's fix is scoped to the getDataConfig(schema)-into-useMemo family (plugin-map, plugin-calendar, plugin-gantt, and plugin-tree/plugin-grid — the latter two deferred/fenced separately). The sweep also surfaced three renderer effects outside that family with the same structural hazard, reported here rather than folded into #6592's diff to keep that PR scoped to its named family.
The shared hazard (background, not new)
useMemo carries no semantic guarantee — React may discard a memo's cache and recompute it even when the dependency array compares equal to the previous render. A fetch effect (or a count-probing effect that calls dataSource.find) whose OWN dependency array names the memoised object itself, rather than the primitive fields it reads off that object, therefore refetches on a discard even though nothing an author or caller controls changed. objectui#6018/#5976/#6591/#6592 is the multi-round campaign fixing this for the dataConfig/mapConfig/schema family specifically; these three are the same shape, different origin.
1. packages/plugin-detail/src/RelatedList.tsx — defaultSortSpec / listFilterNode
const defaultSortSpec = React.useMemo(() => normalizeSortSpec(defaultSort), [defaultSortKey]);
const listFilterNode = React.useMemo(() => toFilterNode(filter), [filterKey]);
Both are already keyed on a JSON.stringify-derived string (defaultSortKey/filterKey) rather than on defaultSort/filter directly — which protects against a render-fresh defaultSort/filter prop, but NOT against useMemo discarding its cache and calling normalizeSortSpec/toFilterNode again, which is free to return a new object even when the key matched. The fetch effect further down lists defaultSortSpec/listFilterNode themselves (not the string keys) in its own dependency array.
2. packages/components/src/renderers/layout/containers.tsx — probeTargets
const probeTargets = React.useMemo(() => { /* builds a Map(...) */ }, [items, recordObject]);
probeTargets is a Map, consumed by identity in the related-count-probing useEffect right below it (if (probeTargets.size === 0) return; for (const probes of probeTargets.values()) { ... ds.find(...) }). A discard reconstructs an equal-content Map with a new identity and re-probes every tab's count.
3. packages/plugin-list/src/ListView.tsx — expandFields
const expandFields = React.useMemo(() => { /* string[] derived from schema.columns */ }, [...]);
Consumed by identity in the $expand-building fetch effect. Not fully triaged against a positive/negative discard-proxy test the way #6592's four fixed files are — flagging for someone to do that triage before treating it as confirmed, since ListView is large, central plumbing and a false positive here would be costly to chase.
Severity
Low — the observable is an extra dataSource.find/count-probe round trip on a memo-cache discard, not incorrect data (unlike #5953/#6014-class bugs, nothing here is a value-equality trap). Worth fixing on the same "useMemo is a pure optimisation, not a correctness dependency" principle #6592 is closing out, not urgent.
Unassigned — filed as a census finding for triage to route, not claimed.
Origin: objectui#6592's census (systematic sweep of every
useEffectwhose dependency array names auseMemo-bound identifier, cross-checked against a fetch-call-site heuristic, positive-controlled against the knownpackages/plugin-mapmember). That card's fix is scoped to thegetDataConfig(schema)-into-useMemofamily (plugin-map,plugin-calendar,plugin-gantt, andplugin-tree/plugin-grid— the latter two deferred/fenced separately). The sweep also surfaced three renderer effects outside that family with the same structural hazard, reported here rather than folded into #6592's diff to keep that PR scoped to its named family.The shared hazard (background, not new)
useMemocarries no semantic guarantee — React may discard a memo's cache and recompute it even when the dependency array compares equal to the previous render. A fetch effect (or a count-probing effect that callsdataSource.find) whose OWN dependency array names the memoised object itself, rather than the primitive fields it reads off that object, therefore refetches on a discard even though nothing an author or caller controls changed. objectui#6018/#5976/#6591/#6592 is the multi-round campaign fixing this for thedataConfig/mapConfig/schemafamily specifically; these three are the same shape, different origin.1.
packages/plugin-detail/src/RelatedList.tsx—defaultSortSpec/listFilterNodeBoth are already keyed on a
JSON.stringify-derived string (defaultSortKey/filterKey) rather than ondefaultSort/filterdirectly — which protects against a render-freshdefaultSort/filterprop, but NOT againstuseMemodiscarding its cache and callingnormalizeSortSpec/toFilterNodeagain, which is free to return a new object even when the key matched. The fetch effect further down listsdefaultSortSpec/listFilterNodethemselves (not the string keys) in its own dependency array.2.
packages/components/src/renderers/layout/containers.tsx—probeTargetsprobeTargetsis aMap, consumed by identity in the related-count-probinguseEffectright below it (if (probeTargets.size === 0) return; for (const probes of probeTargets.values()) { ... ds.find(...) }). A discard reconstructs an equal-contentMapwith a new identity and re-probes every tab's count.3.
packages/plugin-list/src/ListView.tsx—expandFieldsConsumed by identity in the
$expand-building fetch effect. Not fully triaged against a positive/negative discard-proxy test the way #6592's four fixed files are — flagging for someone to do that triage before treating it as confirmed, sinceListViewis large, central plumbing and a false positive here would be costly to chase.Severity
Low — the observable is an extra
dataSource.find/count-probe round trip on a memo-cache discard, not incorrect data (unlike #5953/#6014-class bugs, nothing here is a value-equality trap). Worth fixing on the same "useMemo is a pure optimisation, not a correctness dependency" principle #6592 is closing out, not urgent.Unassigned — filed as a census finding for triage to route, not claimed.