chainRoute
Creates a post-commit readiness route from an already activated route. chainRoute prepares data or state after history has changed; use beforeNavigate when navigation itself must be held.
API
function chainRoute<T>({
route: Route<T>,
beforeOpen: CallableUnit | CallableUnit[],
openOn?: Unit | Unit[],
cancelOn?: Unit | Unit[],
}): ChainRoute<T>beforeOpen accepts existing callable Effector events and effects. Units run in array order; Effects are awaited. No router-specific task primitive is needed. The returned ChainRoute<T> is a pathless route with an additional cancelled event and uses the same createRoute() lifecycle. The deprecated createVirtualRoute result remains accepted as an input for compatibility.
Effect shorthand
When openOn is omitted, the chained route opens as soon as beforeOpen finishes successfully:
const loadUserFx = createEffect(async ({ params }) =>
api.getUser(params.userId),
);
export const readyUserRoute = chainRoute({
route: routes.user,
beforeOpen: loadUserFx,
});An Effect failure cancels the current chained attempt. Observe the reason from the Effect's normal fail or failData units.
Explicit completion and cancellation
Use openOn when preparation starts one flow and readiness arrives through another unit:
const authorized = createEvent();
const rejected = createEvent();
const authorizedRoute = chainRoute({
route: routes.profile,
beforeOpen: checkAuthorizationFx,
openOn: authorized,
cancelOn: rejected,
});If an openOn signal fires while a beforeOpen Effect is still running, it is remembered and the route opens after preparation succeeds. cancelOn closes an already opened chain or cancels a pending one and emits cancelled.
Pending
chained.$isPending is true from the parent route's opened event until one of these terminal states:
- the chained route opens;
- preparation fails;
cancelOnfires;- the parent closes.
This includes time spent waiting for an explicit openOn signal.
sample({
clock: readyUserRoute.$isPending,
target: pageProgress.changed,
});Repeated activation
Repeated parent activation is takeLatest. A new payload starts a new attempt, closes the previously opened derived route, and ignores late Effect results from older attempts.
Chaining concerns
Because the result is a virtual route, readiness can be derived in layers:
const authorized = chainRoute({
route: routes.admin,
beforeOpen: checkRoleFx,
});
const ready = chainRoute({
route: authorized,
beforeOpen: loadAdminDataFx,
});Use this for post-commit authorization/readiness UI, not for protecting a URL from entering history. For a true transition guard, compose beforeNavigate({ controls, to: routes.admin, ... }).
Lifecycle summary
- Parent
openedstarts preparation and pending. - Callable units execute sequentially.
- Successful preparation auto-opens, or waits for
openOnwhen supplied. - Error,
cancelOn, or parent close cancels pending. - A newer parent activation supersedes the older attempt.
See Navigation lifecycle for the pre-/post-commit boundary.