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
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.:
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'sComponentCreator) instead of in its own component scope.BackPageHeroActionscallsuseConfig()(→useContext(ConfigContext)), so when the builder path varies between renders,ComponentCreator's hook count changes and React warns:This is pre-existing (the pattern predates the upgrade —
useConfigwas already auseContexthook 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:
JSX requires
.tsx, so the affected builder files need renaming (git mvto 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.tsbuilder files:app/viewModelBuilders/azul/hca-dcp/common/viewModelBuilders.tsapp/viewModelBuilders/catalog/anvil-catalog/common/viewModelBuilders.tsapp/viewModelBuilders/azul/hca-dcp/common/dataSummaryMapper/dataSummaryMapper.tsapp/viewModelBuilders/azul/lungmap/common/viewModelBuilders.tsMost 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(usesuseConfig).Acceptance
.tsbuilder files renamed to.tsx(viagit mv).C.Component({...})invocations of hook-using components rendered as<C.Component {...} />elements (ideally all 37 for consistency).next build --webpack),npm run lint,npx tsc, and unit tests stay green.