#5346·vendure

useGeneratedColumns can cache a stale display component lookup

Author: michaelbromleyCreated Sep 10, 2026Updated Sep 10, 2026

Describe the bug

useGeneratedColumns reads the display component registry inside a useMemo whose dependency list contains nothing registry-related:

typescript
}, [fields, customizeColumns, rowActions, deleteMutation, additionalColumns,
    defaultColumnOrder, pageId, pageBlock?.blockId]);

The registry is a plain Map with no subscription mechanism, so there is nothing that could be added to that list to make the memo recompute when a component registers.

If every prop in the dependency list is referentially stable across renders, a column that generated before a display component was registered keeps the un-registered renderer permanently. Registering later has no effect at all.

This was confirmed with a probe that mounts the hook, renders once with nothing registered, then registers and forces a re-render:

Referentially stable props  ->  ["core-money-cell", "core-money-cell"]   stale, permanently
Inline literal in any dep   ->  ["core-money-cell", "late-registered"]   recovers

Why it is not currently a live bug

Every existing call site passes at least one inline object or array literal, so the memo recomputes on every render and is effectively inert. For example order-table.tsx:168 memoises customizeColumns but passes rowActions: [] inline, and product-variants-table.tsx passes customizeColumns={{...}} inline.

That makes this a latent trap rather than a present defect. The day someone stabilises those props as a performance improvement, display component overrides silently stop working, and nothing will point at the change that caused it.

Background

Before #4064 the registry was read inside the cell renderer, so it could never go stale. #5339 restores the registry-first precedence that #4064 removed, but reads the registry during column generation rather than at cell-render time. That is the one place #5339 does not faithfully restore the earlier semantics, and it was a deliberate trade-off: routing the custom-cell path back through CellWrapper would undo the direct-call property that #4064 introduced to stop cells unmounting on every table re-render.

Possible directions

  • Give the display component registry a subscription, so consumers can re-render when it changes.
  • Read the registry at cell-render time again, and solve the unmounting problem #4064 addressed some other way.
  • Leave the behaviour as it is and document that useGeneratedColumns callers must not fully stabilise their props.

Additional context

Raised independently by the contributor on #5339, by a review pass on the same PR, and by the verification that preceded merging it. Three separate readings landing on the same point.