Run loaders on navigation - #814
Open
pleek91 wants to merge 5 commits into
Open
Conversation
It compared a copy of a parameter against the parameter it was copied from, so the condition was never true. Its job — keeping a superseded navigation from acting on its own props — is done by the ABANDONED response instead. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Loaders are stored per match alongside views, so a route's data can be derived from its matches the same way its views are.
pleek91
force-pushed
the
run-loaders
branch
3 times, most recently
from
August 3, 2026 21:25
f8e21ba to
c0c8f00
Compare
Loaders are declared the same way views are, and both methods now rebuild through withRouteMethods so chaining one never drops the other. A loader name an ancestor already uses throws, since a route's data combines the loaders of every match.
RouteDataOf combines the loaders of every match, so a child sees an ancestor's data as its own, and flattens to the loader's data directly when the only loader in the tree is unnamed. Carried by ResolvedRouteWithData rather than by ResolvedRoute: only the route being navigated to has data, so a route the router merely resolved would carry a promise that could never settle.
pleek91
force-pushed
the
run-loaders
branch
7 times, most recently
from
August 4, 2026 14:44
cdce248 to
d9970e6
Compare
A view's props getter and a loader are the same thing to the store: a named callback belonging to one match. Both are set into the navigation's store together, and only what waits on them differs — props are awaited as what a view renders with, loaders are awaited only to act on a push or a rejection. The kind is part of the key, so a view and a loader sharing a name on one route stay two values. Data is attached to a route as it becomes current, which is the only route whose data anything is computing. A props getter is given it too, since one loader feeding several views is the reason data exists. A loader is not: a route's data includes what the loader itself is computing, so reading it could only wait on itself, or on a sibling waiting back. What is left reads only upward, so no arrangement of callbacks can wait on itself. The store is no longer only about props, so it is now the route value store.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Sixth step of #805, stacked on #812. This is the one that makes loaders do something.
To the store, a props getter and a loader are the same thing: a named callback belonging to one match. Both are set into the navigation's store together, and only what waits on them differs — props are awaited as what a view renders with, loaders are awaited only so a push or a rejection from one is still acted on. That is what makes loaders non-blocking: nothing on the render path reads them.
The kind is part of the key, so a view and a loader sharing a name on one route stay two values.
Who has data
Data is attached to a route as it becomes current, not as it is resolved — a route is resolved before the navigation that computes its data owns a store, so reading it any earlier subscribes to the store that navigation is about to replace.
RouterRoute—useRoute(),router.routeResolvedRoute—router.resolve,router.find, hookto/fromA props getter has it because one loader feeding several views is the reason data exists:
A loader does not. A route's data includes what its own loaders are computing, so a loader reading it could only wait on itself, or on a sibling waiting back. Its route is a plain
ResolvedRoute, and readingdataanyway throwsLoaderDataAccessErrorrather than hanging.Both reach other routes through
parent.propsandparent.data, which point only upward:So the dependency graph is acyclic by construction: loaders read upward, props read upward and their own route, nothing reads downward.
Since the store is no longer only about props,
createPropStoreis nowcreateRouteValueStore(andusePropStorefollows). "Value" is the word the store already used internally —getValue,ValueLocation, and #806's own title — and it stays distinct fromroute.data, which means loader data specifically. Nothing renamed here is exported from the package.Worth a look
createRouterRoutesnapshots the resolved route in a spread, so anything that changes per navigation has to be passed as a ref.datanow is.titlehas the same problem and is left alone here.reactive()'s unwrap types cannot see through a generic route'sdata(it is a promise), socreateRouterRouteasserts its return shape instead of inferring it.Prefetching loaders is the next PR: the
loadersprefetch setting does not exist yet, so a loader only runs on navigation.