| name | reactor-navigation |
| description | Reactor navigation — `UseNavigation` hook, `NavigationHost` renderer, `NavigationView` sidebar, `TabView` tabs, route params, stack operations, page lifecycle, deep linking, transitions, caching. Use when building multi-page apps or wiring sidebar/tab navigation. |
Navigation in Reactor
Reactor uses a stack-based navigation model with type-safe routes. You
define routes as an enum, create a navigation handle with UseNavigation,
and render the current page with NavigationHost. The system manages
back/forward stacks, transitions, caching, and deep linking.
Quick reference
| API | Purpose |
|---|
UseNavigation(Route.Home) | Create a NavigationHandle<Route> (call once at root) |
UseNavigation<Route>() | Retrieve the nearest ancestor's handle (child components) |
NavigationHost(nav, route => ...) | Render the current page |
NavigationView([items], content) | Sidebar navigation with icons |
TabView(Tab(...), Tab(...)) | Tab-based parallel navigation |
UseNavigationLifecycle(...) | Page appear/disappear callbacks |
UseSystemBackButton(nav, window) | Wire system back button to nav stack |
DeepLinkMap<Route> | Map URI patterns to routes |
1. Defining routes
Use an enum. Each value is a distinct page:
enum Route { Home, Settings, Profile, Details }
For routes that carry data, use a discriminated-union pattern with records:
abstract record Route
{
public record Home : Route;
public record Settings : Route;
public record Details(int ItemId) : Route;
}
2. Basic navigation
class App : Component
{
public override Element Render()
{
var nav = UseNavigation(Route.Home);
return VStack(12,
HStack(8,
Button("Home", () => nav.Navigate(Route.Home)),
Button("Settings", () => nav.Navigate(Route.Settings)),
Button("Back", () => nav.GoBack()).IsEnabled(nav.CanGoBack)
),
NavigationHost(nav, route => route switch
{
Route.Home => Component<HomePage>(),
Route.Settings => Component<SettingsPage>(),
_ => TextBlock("Not found")
})
);
}
}
Key points:
UseNavigation(Route.Home) — call once at the root with the initial route.
nav.Navigate(route) — pushes onto the back stack.
nav.GoBack() / nav.GoForward() — stack navigation.
NavigationHost(nav, route => ...) — renders the matched element.
3. Stack operations
| Method | Effect |
|---|
Navigate(route) | Push route onto back stack |
GoBack() | Pop current, return to previous |
GoForward() | Move forward (after going back) |
Replace(route) | Swap current route without touching the stack |
Reset(route) | Clear all stacks, start fresh |
PopTo(predicate) | Pop until a matching route is found |
Properties: CurrentRoute, CanGoBack, CanGoForward, BackStack,
ForwardStack, Depth.
Use Reset for sign-out flows to prevent navigating back to authenticated
pages.
4. NavigationView (sidebar)
var nav = UseNavigation(Route.Home);
return NavigationView(
[
NavItem("Home", icon: "Home", tag: "Home"),
NavItem("Settings", icon: "Setting", tag: "Settings"),
NavItem("Profile", icon: "Contact", tag: "Profile")
],
content: NavigationHost(nav, route => route switch
{
Route.Home => VStack(Heading("Home"), TextBlock("Welcome.")).Padding(24),
Route.Settings => VStack(Heading("Settings"), TextBlock("Configure.")).Padding(24),
Route.Profile => VStack(Heading("Profile"), TextBlock("Your info.")).Padding(24),
_ => TextBlock("Not found").Padding(24)
})
);
NavItem(label, icon?, tag?) creates sidebar items. Icons are Segoe Fluent
Icons names. The NavigationView handles selection state.
Manual wiring (without UseNavigation):
SelectedTag and OnSelectionChanged are record properties — use with { }.
For the settings item: set IsSettingsVisible = true and check args.IsSettingsSelected (not via tag).
Use .Set(nv => nv.PaneDisplayMode = ...) for non-record properties.
5. TabView (parallel tabs)
Unlike stack navigation, tabs keep all content alive simultaneously:
return TabView(
Tab("Documents", VStack(12,
TextBlock("Your documents."),
Button("New", () => { })).Padding(24)),
Tab("Recent", TextBlock("Recently opened.").Padding(24)),
Tab("Shared", TextBlock("Shared files.").Padding(24))
);
Use tabs when users switch freely between parallel workspaces.
6. Page lifecycle
UseNavigationLifecycle fires callbacks when a page appears or disappears:
UseNavigationLifecycle(
onNavigatedTo: ctx => LoadData(ctx.PreviousRoute),
onNavigatingFrom: ctx =>
{
if (hasUnsavedChanges) ctx.Cancel();
},
onNavigatedFrom: ctx => SaveDraft()
);
| Callback | When |
|---|
onNavigatedTo | After this page becomes active — fetch data, start timers |
onNavigatingFrom | Before leaving — call ctx.Cancel() to block navigation |
onNavigatedFrom | After leaving — save drafts, stop timers |
7. Page transitions
Set Transition on NavigationHost:
NavigationHost(nav, route => ...) with
{
Transition = NavigationTransition.DrillIn()
}
| Transition | Effect |
|---|
NavigationTransition.Slide() | Slide + fade (default) |
NavigationTransition.Fade() | Crossfade |
NavigationTransition.DrillIn() | Scale + fade — use for list→detail |
NavigationTransition.Spring() | Spring-physics slide |
NavigationTransition.None | Instant swap |
GoBack automatically reverses direction. Transitions run on the compositor
thread.
8. Deep linking
DeepLinkMap maps URI patterns to route constructors:
var map = UseMemo(() => new DeepLinkMap<Route>()
.Map("/", _ => Route.Home)
.Map("/settings", _ => Route.Settings)
.Map("/users/{id:int}/posts/{postId:int}", args =>
{
var userId = args.Get<int>("id");
var postId = args.Get<int>("postId");
return Route.Details;
}, backStackFactory: () => new[] { Route.Home }));
map.Resolve(uri);
Pattern segments:
| Segment | Matches |
|---|
/literal | Exact match |
/{param} | String capture |
/{param:int} | Typed capture (int, long, bool, Guid) |
/{param?} | Optional parameter |
/{**} | Wildcard — remaining path |
?key=value | Query string parameters |
backStackFactory builds a synthetic back stack so GoBack works after
deep-linked entry.
9. Page caching
NavigationHost(nav, route => ...) with
{
CacheMode = NavigationCacheMode.Enabled,
CacheSize = 5
}
| Cache mode | Behavior |
|---|
Disabled | Always unmount/remount (default) |
Enabled | LRU cache up to CacheSize entries |
Required | Always cached, never evicted |
Caching preserves scroll position, text input, and component state.
10. State serialization
var state = nav.GetState();
nav.SetState(state);
Reactor returns a plain NavigationState<TRoute> POCO and lets you pick the
storage format. For JSON persistence pair the snapshot with a
JsonSerializerContext (AOT-safe):
[JsonSerializable(typeof(NavigationState<AppRoute>))]
partial class AppJsonContext : JsonSerializerContext { }
var json = JsonSerializer.Serialize(nav.GetState(), AppJsonContext.Default.NavigationStateAppRoute);
nav.SetState(JsonSerializer.Deserialize(json, AppJsonContext.Default.NavigationStateAppRoute)!);
Use for persisting navigation across app restarts.
11. System back button
UseSystemBackButton(nav, window);
Wires the Windows title-bar back button and hardware back button to your
navigation stack automatically.
Critical gotchas
- Call
UseNavigation(initial) once at the root. Children retrieve the
same handle via UseNavigation<Route>() (no initial value) through
context.
- Use enums for routes. Compile-time safety — can't navigate to a
non-existent route.
- Use
Reset for sign-out. Clears the stack so users can't go back.
- DrillIn for list→detail. Signals hierarchy; pairs with connected
animation.
ctx.Cancel() in onNavigatingFrom — the only way to guard
against unsaved-changes navigation.
- Don't forget
backStackFactory in deep links. Without it, GoBack
does nothing after a deep-linked entry.