Skip to content

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 ​

ts
function chainRoute<T>(props: ChainRouteProps<T>): 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. ChainRouteProps<T> and ChainRoute<T> are exported public types.

Effect shorthand ​

When openOn is omitted, the chained route opens as soon as beforeOpen finishes successfully:

ts
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:

ts
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;
  • cancelOn fires;
  • the parent closes.

This includes time spent waiting for an explicit openOn signal.

ts
sample({
  clock: readyUserRoute.$isPending,
  target: pageProgress.changed,
});

In the React, Vue, and Solid bindings the same store backs the loading component of a route view, which renders a skeleton in the view's slot while the chain prepares — see createRouteView and the nested loading guide.

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:

ts
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 ​

  1. Parent opened starts preparation and pending.
  2. Callable units execute sequentially.
  3. Successful preparation auto-opens, or waits for openOn when supplied.
  4. Error, cancelOn, or parent close cancels pending.
  5. A newer parent activation supersedes the older attempt.

See Navigation lifecycle for the pre-/post-commit boundary.

Released under the MIT License.