Skip to content

refactor: render view-builder components as JSX elements instead of plain function calls (rules-of-hooks) [low priority] #4877

Description

@frano-m

Priority: low. Tech-debt cleanup surfaced during the Next 16 + findable-ui 54 upgrade (#4876).

Problem

Our view-model builders invoke component-builder functions directly, e.g.:

// app/viewModelBuilders/azul/hca-dcp/common/viewModelBuilders.ts:2159
return C.BackPageHeroActions({
  callToActionProps: { ... },
  ...
});

C.BackPageHeroActions({...}) calls the component as a plain function rather than rendering it as an element (<C.BackPageHeroActions {...} />). For components that use React hooks this violates the Rules of Hooks: the hook executes inside whatever is rendering the builder output (findable-ui's ComponentCreator) instead of in its own component scope.

BackPageHeroActions calls useConfig() (→ useContext(ConfigContext)), so when the builder path varies between renders, ComponentCreator's hook count changes and React warns:

React has detected a change in the order of Hooks called by ComponentCreator.
   Previous render            Next render
   ...
6. undefined                  useContext

This is pre-existing (the pattern predates the upgrade — useConfig was already a useContext hook in findable-ui 53). It was observed once during a dev session and cleared on a full refresh, so it currently presents as a Fast-Refresh/HMR artifact rather than a hard failure — but the underlying anti-pattern is real and worth removing so it can't bite on a genuine re-render.

Fix

Where a builder returns a hook-using component, render it as a JSX element instead of calling it:

// rename file .ts -> .tsx, then:
return <C.BackPageHeroActions callToActionProps={{ ... }} ... />;

JSX requires .tsx, so the affected builder files need renaming (git mv to preserve history). Alternatively, React.createElement(C.BackPageHeroActions, { ... }) works without renaming, but .tsx + JSX is cleaner and consistent with the rest of the views.

Scope

The C.Component({...}) function-call pattern appears at 37 call sites across 4 .ts builder files:

File Call sites
app/viewModelBuilders/azul/hca-dcp/common/viewModelBuilders.ts 20
app/viewModelBuilders/catalog/anvil-catalog/common/viewModelBuilders.ts 15
app/viewModelBuilders/azul/hca-dcp/common/dataSummaryMapper/dataSummaryMapper.ts 1
app/viewModelBuilders/azul/lungmap/common/viewModelBuilders.ts 1

Most of these invoke hookless presentational builders (cells, values) where the call form is harmless in practice — but converting uniformly to JSX elements removes the foot-gun entirely and prevents the next hook-using component from silently reintroducing the violation. The confirmed hook-using case to prioritise is C.BackPageHeroActions (uses useConfig).

Acceptance

  • Affected .ts builder files renamed to .tsx (via git mv).
  • C.Component({...}) invocations of hook-using components rendered as <C.Component {...} /> elements (ideally all 37 for consistency).
  • No "change in order of Hooks" warning when navigating between project detail pages.
  • Build (next build --webpack), npm run lint, npx tsc, and unit tests stay green.

Metadata

Metadata

Assignees

Labels

debt[type] A defect incurring continued engineering cost

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions