Skip to content

Commit b01f92a

Browse files
fix(console): preload object/dashboard/report/page in AppContent to avoid hook-order crash
Agent-Logs-Url: https://github.com/objectstack-ai/objectui/sessions/7fce824c-10a5-4953-8cfd-92d1f64f54a3 Co-authored-by: xuyushun441-sys <255036401+xuyushun441-sys@users.noreply.github.com>
1 parent a4263ac commit b01f92a

2 files changed

Lines changed: 48 additions & 2 deletions

File tree

CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3030

3131
### Fixed
3232

33+
- **"Object Not Found" / React "Rendered more hooks" crash on app entry.**
34+
A regression introduced by the lazy `MetadataProvider` refactor above:
35+
`useMetadata().objects` started empty while the lazy fetch was in flight,
36+
so `ObjectView` hit its `if (!objectDef) return <Empty/>` early return on
37+
the first render; once the fetch resolved, the hooks declared below that
38+
early return ran and React threw _"Rendered more hooks than during the
39+
previous render"_. `AppContent` now preloads the `object`, `dashboard`,
40+
`report`, and `page` buckets (via `ensureType`) before rendering the
41+
routes under `/apps/:appName/*`, so legacy components that assume
42+
metadata is fully loaded by render time continue to work. `/home`,
43+
`/login` and `/register` do not go through `AppContent`, so the Phase 1
44+
benefit of fetching only the `app` list at boot is preserved.
45+
3346
- **Record detail header** no longer renders two separate "More" (⋯) overflow
3447
menus when an object defines more `record_header` actions than
3548
`maxVisible`. The hardcoded `<DropdownMenu>` inside

apps/console/src/App.tsx

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,9 +99,42 @@ export function AppContent() {
9999
const navigate = useNavigate();
100100
const location = useLocation();
101101
const { appName } = useParams();
102-
const { apps, objects: allObjects, loading: metadataLoading } = useMetadata();
102+
const { apps, objects: allObjects, loading: metadataLoading, ensureType } = useMetadata();
103103
const { t } = useObjectTranslation();
104104
const { objectLabel } = useObjectLabel();
105+
106+
// Preload the metadata buckets that the routes under /apps/:appName/* assume
107+
// are fully loaded by render time. The Phase-1 MetadataProvider refactor
108+
// made these buckets lazy (only `app` is eager); without this preload some
109+
// legacy consumers (notably `ObjectView`) would mount with `objects=[]`,
110+
// hit an `if (!objectDef) return <Empty/>` early-return, then break React's
111+
// rules-of-hooks once the lazy fetch resolves and the hooks below the early
112+
// return start running. /home and /login don't go through AppContent, so
113+
// they keep the Phase-1 benefit of fetching only the `app` list at boot.
114+
//
115+
// `ensureType` is optional at the destructure site so that the many test
116+
// files which provide a partial `useMetadata()` mock continue to work
117+
// unchanged; in those environments the preload is a no-op (the mocked
118+
// metadata arrays are already populated synchronously).
119+
const [scopeMetaReady, setScopeMetaReady] = useState(!ensureType);
120+
useEffect(() => {
121+
if (!ensureType) {
122+
setScopeMetaReady(true);
123+
return;
124+
}
125+
let cancelled = false;
126+
Promise.all([
127+
ensureType('object'),
128+
ensureType('dashboard'),
129+
ensureType('report'),
130+
ensureType('page'),
131+
]).finally(() => {
132+
if (!cancelled) setScopeMetaReady(true);
133+
});
134+
return () => {
135+
cancelled = true;
136+
};
137+
}, [ensureType]);
105138

106139
// Determine active app based on URL
107140
const activeApps = apps.filter((a: any) => a.active !== false);
@@ -253,7 +286,7 @@ export function AppContent() {
253286
[user, activeApp, editingRecord]
254287
);
255288

256-
if (!dataSource || metadataLoading) return <LoadingScreen />;
289+
if (!dataSource || metadataLoading || !scopeMetaReady) return <LoadingScreen />;
257290

258291
// Allow create-app route even when no active app exists
259292
const isCreateAppRoute = location.pathname.endsWith('/create-app');

0 commit comments

Comments
 (0)