Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/detail-section-hooks-order.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
---
'@object-ui/plugin-detail': patch
---

Fix a React #300 crash when drilling from a master record into a related child record.

`DetailSection` placed its all-empty `return null` guard *before* the virtual-scroll `useEffect`, so a section that rendered all-empty on one pass (effect skipped) and populated on the next (effect runs) changed its hook count between renders of the same reconciled fiber — React threw error #300 ("rendered more hooks than during the previous render"). This reliably tripped on the master-detail drill-in (e.g. Account → Project), showing an error boundary and bouncing the user away on refresh. The all-empty guard now runs after every hook, making the hook count invariant.
15 changes: 12 additions & 3 deletions packages/plugin-detail/src/DetailSection.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -179,9 +179,6 @@ export const DetailSection: React.FC<DetailSectionProps> = ({
? section.fields.filter((field) => !isEmptyValue(field))
: section.fields;

// Hide entire section when all fields are empty AND user did not request to show them.
if (visibleFields.length === 0 && emptyCount === section.fields.length) return null;

// Apply auto-layout: infer columns and auto-span wide fields
const { fields: layoutFields, columns: rawColumns } = applyDetailAutoLayout(
visibleFields,
Expand DownExpand Up@@ -450,6 +447,18 @@ export const DetailSection: React.FC<DetailSectionProps> = ({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [vsEnabled, layoutFields.length, vsBatchSize]);

// Hide entire section when all fields are empty AND the user has not asked to
// reveal them. This early return MUST come AFTER every hook above (including
// the virtual-scroll useEffect) — never before. When a section is all-empty
// on one render (early return, N hooks) but has data on the next render (the
// useEffect runs, N+1 hooks) of the SAME reconciled fiber, the hook count
// changes between renders and React throws error #300 ("rendered more hooks
// than during the previous render"). This is the master-detail drill-in
// crash: navigating account → project reuses this DetailSection fiber, and
// its sections flip from empty to populated. Keeping the guard below all
// hooks makes the hook count invariant.
if (visibleFields.length === 0 && emptyCount === section.fields.length) return null;

const renderedFields = visibleCount !== undefined
? layoutFields.slice(0, visibleCount)
: layoutFields;
Expand Down
Loading