Summary
When navigating to a nested route whose parent declares a load function, I'm seeing components that are mounted inside the parent's render (or persistent frame components that read the URL directly) render against a cold cache — the URL commit / transition appears to happen before the parent load's awaited work has settled.
Context / repro
In our app, the route tree looks roughly like:
routeWithAppContext(true, {
render: (children) => <BusinessFrame>{children}</BusinessFrame>,
async load(_entry, context) {
await context.graphql.cache.query(frameQuery);
},
children: [
routeWithAppContext('/activities', {
children: [
routeWithAppContext(':id', {
async load({context, matched}) {
await Promise.all([
BusinessActivityDetailPage.load(),
context.graphql.cache.query(businessActivityFrameQuery, {
variables: {id: GlobalID.serialize('Activity', matched)},
}),
]);
},
render: (children) => <BusinessActivityDetailPage>{children}</BusinessActivityDetailPage>,
children: [
routeWithAppContext('/', { /* overview — its own load */ }),
routeWithAppContext('/schedule', { children: [/* ... */] }),
routeWithAppContext('/settings', { /* ... */ }),
],
}),
],
}),
],
});
BusinessFrame renders a persistent side nav that includes a contextual mini-nav component. That component reads the URL via useRelativePathname(), extracts the activity id, and constructs a model that calls context.graphql.cache.create(activityFrameQuery).run({id}) — the same query the parent load preloads.
On a cold navigation to /activities/:id/<something>, the mini-nav and the BusinessActivityDetailPage frame render with activity as null (the query observable hasn't resolved) for a beat, then pop in. The data is being preloaded — calling cache.query(...) in the parent's load and awaiting it — but the transition seems to commit before that preload is actually in the cache that cache.create(...).run(...) reads from.
We worked around it by duplicating the cache.query(businessActivityFrameQuery, ...) call in each of the leaf child routes' loads. That makes the pop-in go away, which matches the theory: the child load is blocking the transition correctly, the parent load isn't.
Expected
Route transitions shouldn't commit / render until every matched ancestor route's load has resolved. If parent loads are intentionally fire-and-forget relative to child transitions, that'd be worth documenting — but the surprise is large enough that I suspect it's a bug.
Questions for triage
- Are parent and child
loads supposed to run in parallel and both gate the transition, or does the framework only gate on the leaf?
- If parallel-gating is the intent, is there a case where the parent's awaited side effects (like hydrating a query cache) aren't visible to the already-committed render?
Happy to provide a minimal repro if useful — just wanted to surface the shape of the issue first.
Summary
When navigating to a nested route whose parent declares a
loadfunction, I'm seeing components that are mounted inside the parent'srender(or persistent frame components that read the URL directly) render against a cold cache — the URL commit / transition appears to happen before the parentload's awaited work has settled.Context / repro
In our app, the route tree looks roughly like:
BusinessFramerenders a persistent side nav that includes a contextual mini-nav component. That component reads the URL viauseRelativePathname(), extracts the activity id, and constructs a model that callscontext.graphql.cache.create(activityFrameQuery).run({id})— the same query the parentloadpreloads.On a cold navigation to
/activities/:id/<something>, the mini-nav and theBusinessActivityDetailPageframe render withactivityasnull(the query observable hasn't resolved) for a beat, then pop in. The data is being preloaded — callingcache.query(...)in the parent'sloadand awaiting it — but the transition seems to commit before that preload is actually in the cache thatcache.create(...).run(...)reads from.We worked around it by duplicating the
cache.query(businessActivityFrameQuery, ...)call in each of the leaf child routes'loads. That makes the pop-in go away, which matches the theory: the childloadis blocking the transition correctly, the parentloadisn't.Expected
Route transitions shouldn't commit / render until every matched ancestor route's
loadhas resolved. If parentloads are intentionally fire-and-forget relative to child transitions, that'd be worth documenting — but the surprise is large enough that I suspect it's a bug.Questions for triage
loads supposed to run in parallel and both gate the transition, or does the framework only gate on the leaf?Happy to provide a minimal repro if useful — just wanted to surface the shape of the issue first.