navigate - greydragon888/real-router GitHub Wiki

router.navigate

1. Overview

  • What it does: Performs programmatic navigation to the specified route, triggering the full transition lifecycle (guards, events).
  • When to use: For programmatic navigation between pages/states in response to user actions or application logic.

2. Signature

router.navigate(name: string): Promise<State>;
router.navigate(name: string, params: Params): Promise<State>;
router.navigate(name: string, params: Params, opts: NavigationOptions): Promise<State>;

Usage Examples

// Basic navigation (fire-and-forget)
router.navigate("users");

// Navigation with parameters
router.navigate("users.view", { id: 123 });

// Navigation with result handling
const state = await router.navigate("profile");
console.log("Navigated to:", state.name);

// Navigation with error handling
try {
  const state = await router.navigate("profile");
  console.log("Navigated to:", state.name);
} catch (err) {
  console.error("Navigation failed:", err.code);
}

// Navigation with options
const state = await router.navigate(
  "orders.view",
  { id: 42 },
  { replace: true, reload: true },
);
console.log("Arrived at:", state.path);

// Concurrent navigation cancels previous
router.navigate("slow-route", { id: 1 });
router.navigate("fast-route"); // Previous promise rejects with TRANSITION_CANCELLED

// Cancel via AbortController
const controller = new AbortController();
router.navigate("route", {}, { signal: controller.signal });
controller.abort(); // Promise rejects with TRANSITION_CANCELLED

// Timeout via AbortSignal.timeout()
await router.navigate("slow-route", {}, { signal: AbortSignal.timeout(5000) });

3. Parameters

Call Forms (polymorphic method)

Form Description
navigate(name) Simple navigation without parameters
navigate(name, params) Navigation with route parameters
navigate(name, params, opts) Navigation with parameters and options

Argument Descriptions

name: string (required)

  • Purpose: Target route name (dot notation for nested routes)
  • Allowed values: String matching a registered route
  • Examples: "users", "orders.view", "settings.profile"
  • Error behavior:
    • "" (empty string) -> ROUTE_NOT_FOUND via Promise rejection
    • null/undefined/number -> TypeError via Promise rejection
    • Non-existent route -> ROUTE_NOT_FOUND via Promise rejection + TRANSITION_ERROR event

params?: Params (optional)

  • Purpose: Route parameters (substituted in URL)
  • Allowed values: Object with string keys and values: string | number | boolean | null | array
  • Default value: {}
  • Error behavior:
    • null/undefined -> interpreted as {}
    • NaN, Infinity, -Infinity values in params -> Promise rejects with TypeError (not serializable)

undefined values in params are stripped. router.navigate("x", { a: 1, b: undefined }) produces the same URL and state.params as router.navigate("x", { a: 1 }) — the b key is absent from both. This contract is guaranteed by @real-router/core itself (not by the query-string engine), so it also holds when plugins inject undefined via forwardState interceptors.

null and "" are distinct, defined values — they are NOT stripped:

Value URL result state.params after navigation
undefined stripped key absent
null ?key (key-only, via nullFormat: "default") null
"" (empty string) ?key= (explicit empty value) ""
0, false ?key=0 / ?key=false (falsy-defined) preserved

See Params Contract in core README for the full type → URL table.

opts?: NavigationOptions (optional)

  • Purpose: Transition control options
  • Default value: {}
  • Error behavior:
    • Invalid type (string, number, array) -> TypeError with message [router.navigate] Invalid options
    • Invalid field types (e.g., replace: "true") -> TypeError

Options (NavigationOptions)

Option Type Default Description
replace boolean false Replace current history entry instead of adding new one
reload boolean false Reload route even if states are equal (triggers lifecycle)
force boolean false Force transition, ignoring state equality check
forceDeactivate boolean false Skip canDeactivate guards (data loss risk!)
redirected boolean false Internal: redirect flag (set automatically)
signal AbortSignal undefined Abort signal for external cancellation (see NavigationOptions)

4. Return Value

  • Type: Promise<State>
  • Successful result: Resolves with State — the new router state after navigation
  • Unsuccessful result: Rejects with RouterError
  • Special cases:
    • For ROUTER_NOT_STARTED, ROUTE_NOT_FOUND, SAME_STATES — Promise rejects, no events emitted for ROUTER_NOT_STARTED
    • For cancellation — starting a new navigation cancels the previous one; stop(), dispose(), or aborting an external AbortSignal also cancel in-flight transitions. The Promise rejects with TRANSITION_CANCELLED

5. Side Effects

  • State change: On successful transition router.setState(newState) is called
  • Event triggers (in order):
    1. TRANSITION_START — before executing guards
    2. TRANSITION_SUCCESS — after successful transition
    3. TRANSITION_ERROR — on error (guard block, route not found, etc.)
    4. TRANSITION_CANCEL — on transition cancellation
  • Browser impact: When using browser plugin — URL and history changes

6. Possible Errors

Condition Error Code Behavior How to Handle
Router not started ROUTER_NOT_STARTED Promise rejects with error, no events Check router.isActive()
Route not found ROUTE_NOT_FOUND Promise rejects + TRANSITION_ERROR event Check route name
Current and target states equal SAME_STATES Promise rejects + TRANSITION_ERROR event Use reload: true or force: true
Guard canActivate returned false CANNOT_ACTIVATE Promise rejects + TRANSITION_ERROR event Check guard logic
Guard canDeactivate returned false CANNOT_DEACTIVATE Promise rejects + TRANSITION_ERROR event Use forceDeactivate: true
Transition cancelled TRANSITION_CANCELLED Promise rejects + TRANSITION_CANCEL event Expected with concurrent navigation, signal.abort(), stop(), or dispose()
Invalid name (null/undefined) Promise rejects with TypeError Validate input data
Invalid opts (not object) Promise rejects with TypeError Validate options
Invalid params (NaN/Infinity) Promise rejects with TypeError Use finite numbers

For declarative error handling without try/catch, see RouterErrorBoundary.

7. Related Methods

Method When to use
router.navigateToDefault() Navigate to default route (from router settings)
router.navigateToNotFound() Set not-found state synchronously (e.g., after API 404)
getLifecycleApi(router).addActivateGuard() Register guard for route entry
getLifecycleApi(router).addDeactivateGuard() Register guard for route exit
router.stop() Stop router (cancels in-flight navigation)
router.dispose() Permanently terminate router

8. Behavior

Main Scenarios

  • Successful navigation updates router.getState() and the returned Promise resolves with state
  • Navigation with parameters substitutes them in URL path: navigate("users.view", { id: 123 }) -> /users/view/123
  • Navigation to nested routes invokes guards of all segments in chain
  • With reload: true or force: true — transition executes even to current state
  • Route default params are extended with passed parameters

Edge Cases

  • Navigating from UNKNOWN_ROUTE: When the current state is UNKNOWN_ROUTE (set by navigateToNotFound()), navigate() automatically forces replace: true even if not explicitly passed. This prevents the browser history from accumulating not-found entries that the user would cycle through with the back button.
  • Parallel navigations: When starting new navigation during async transition — previous transition is cancelled (TRANSITION_CANCELLED) and previous navigation's internal AbortController is aborted
  • Calling router.stop() during guard: Transition is cancelled with TRANSITION_CANCELLED and internal AbortController is aborted
  • Pre-aborted signal: If opts.signal is already aborted, navigation rejects immediately without starting a transition
  • AbortError in guards: DOMException("AbortError") thrown in guards (e.g., from fetch()) is auto-converted to TRANSITION_CANCELLED
  • Promise in guard resolves after cancellation: Ignored (Promise not resolved/rejected again)
  • Dot notation edge cases (.users, users., users..view) — ROUTE_NOT_FOUND
  • Case-sensitive: Routes are case-sensitive (Users != users)

Guarantees

  • Promise resolves or rejects exactly once (either with state or error)
  • TRANSITION_START always precedes other transition events
  • After TRANSITION_SUCCESSrouter.getState() contains new state
  • Guards are replaced, not accumulated when re-registering on same route
  • State objects are frozen (immutable)

Migration from router5

Version Comparison

router5 (master) Real Router (current)
Signature navigate(...args): void | CancelFn navigate(name: string, params?: Params, opts?: NavigationOptions): Promise<State>
Typing JavaScript (no types) Full TypeScript typing
Export export default function withNavigation export function withNavigation

Breaking Changes

Severity What Changed Was Now Impact
CRITICAL Promise-based API Callback-based: navigate(name, cb) returns CancelFn Promise-based: navigate(name) returns Promise<State> All callback-based navigate calls must be rewritten
CRITICAL CancelFn removed const cancel = router.navigate(...) returned cancel function Concurrent navigate() cancels previous; use stop()/dispose() All cancel patterns must be rewritten
CRITICAL Redirects from guards Guards could return { redirect: { name, params } } for redirect Use route config canActivate (ActivationFn) for redirects Code with redirects in guards will break
CRITICAL Error format { code: string } — plain object RouterError — class with methods Check err.code works, but instanceof will change
HIGH transitionToState method Public method on router Renamed to getPluginApi(router).navigateToState() Code with router.transitionToState() will break
HIGH Guard registration API canActivate / canDeactivate getLifecycleApi(router).addActivateGuard() / .addDeactivateGuard() Guard registration calls must be renamed
MEDIUM Options validation No validation — any values accepted Strict field type validation Invalid options will throw TypeError
MEDIUM Params validation No validation NaN/Infinity rejected with TypeError Code with NaN in params will break
MEDIUM Name validation No validation — silently failed null/undefined rejected with TypeError Early error detection
LOW TRANSITION_START event After cancel() of previous Before cancel() Event order changed
LOW meta.redirected Always false Removed — use state.transition?.redirected instead Check redirect status via transition field

Migration Examples

Promise API replacing Callbacks (CRITICAL)

// Code that will break (router5 / old Real Router)
router.navigate("profile", (err, state) => {
  if (err) {
    console.error("Navigation failed:", err.code);
  } else {
    console.log("Navigated to:", state.name);
  }
});

// Code after migration (Real Router)
try {
  const state = await router.navigate("profile");
  console.log("Navigated to:", state.name);
} catch (err) {
  console.error("Navigation failed:", err.code);
}

CancelFn Removed (CRITICAL)

// Code that will break (router5 / old Real Router)
const cancel = router.navigate("slow-page", { id: 1 });
// ... later
cancel(); // CancelFn no longer returned!

// Code after migration (Real Router)
// Option 1: Concurrent navigation cancels previous
router.navigate("slow-page", { id: 1 });
router.navigate("other-page"); // Previous promise rejects with TRANSITION_CANCELLED

// Option 2: Cancel via AbortController
const controller = new AbortController();
router.navigate("slow-page", { id: 1 }, { signal: controller.signal });
controller.abort(); // cancels this specific navigation

// Option 3: Stop the router (cancels in-flight transition)
router.navigate("slow-page", { id: 1 });
router.stop();

// Option 4: Permanently terminate (cancels + prevents restart)
router.dispose();

Redirects from Guards (CRITICAL)

import { getPluginApi } from "@real-router/core/api";
// Code that will break (router5)
router.canActivate("protected", () => (toState, fromState, done) => {
  if (!isAuthenticated) {
    // In router5 this triggered navigation to login
    done({ redirect: { name: "login", params: {} } });
    return;
  }
  done();
});

// Code after migration (Real Router)
// Use route config canActivate (ActivationFn) for redirects
const routes = [
  {
    name: "protected",
    path: "/protected",
    canActivate: (router) => (toState, fromState) => {
      if (!isAuthenticated) {
        // ActivationFn can return State for redirect
        return getPluginApi(router).buildState("login");
      }
      return true;
    },
  },
];

Guard Registration API (HIGH)

import { getLifecycleApi } from "@real-router/core/api";

// Code that will break (router5 / old Real Router)
router.canActivate("route", guardFn);
router.canDeactivate("route", guardFn);

// Code after migration (Real Router)
const lifecycle = getLifecycleApi(router);
lifecycle.addActivateGuard("route", guardFn);
lifecycle.addDeactivateGuard("route", guardFn);

transitionToState Method (HIGH)

import { getPluginApi } from "@real-router/core/api";

// Code that will break (router5)
router.transitionToState(toState, fromState, opts, callback);

// Code after migration (Real Router)
const pluginApi = getPluginApi(router);
const state = await pluginApi.navigateToState(toState, fromState, opts);
// Promise-based, no callback or emitSuccess parameter

Options Validation (MEDIUM)

// Code that will break (Real Router)
router.navigate("users", {}, { replace: "true" }); // TypeError!
router.navigate("users", {}, "options"); // TypeError!

// Correct code
router.navigate("users", {}, { replace: true }); // boolean, not string

Migration Checklist

  • Replace callback-based navigate with async/await and try/catch
  • Replace CancelFn pattern (const cancel = router.navigate(...)) — use concurrent navigation, stop(), or dispose()
  • Remove redirects from guards — use route config canActivate (ActivationFn can return State)
  • Replace canActivate with getLifecycleApi(router).addActivateGuard()
  • Replace canDeactivate with getLifecycleApi(router).addDeactivateGuard()
  • Replace transitionToState with getPluginApi(router).navigateToState()
  • Check options typesreplace, reload, force must be boolean
  • Check params for NaN/Infinity — will be rejected
  • Update error handling — err is now RouterError (but err.code works)

Finding Problematic Code

# Find callback-based navigate calls
grep -rn "\.navigate(" --include="*.ts" --include="*.js" | grep -E "\(err|callback|done\)"

# Find CancelFn patterns
grep -rn "const cancel = .*\.navigate(" --include="*.ts" --include="*.js"

# Find old guard registration API
grep -rn "\.canActivate\|\.canDeactivate" --include="*.ts" --include="*.js"

# Find redirects in guards
grep -r "redirect:" --include="*.ts" --include="*.js" | grep -E "canActivate|canDeactivate"

# Find old cancel patterns
grep -r "\.cancel()" --include="*.ts" --include="*.js"

# Find transitionToState
grep -r "transitionToState" --include="*.ts" --include="*.js"

# Find invalid options
grep -r "replace: ['\"]" --include="*.ts" --include="*.js"
⚠️ **GitHub.com Fallback** ⚠️