Routing and Navigation in euv
Project Code:https://github.com/euv-dev/euv
Routing and navigation are fundamental aspects of any single-page application (SPA). They determine how users move between different views and how the application's UI reflects the current state. In euv, routing is handled through a combination of browser APIs, reactive signals, and the framework's component system.
This article provides a comprehensive guide to implementing routing and navigation in euv applications, from basic navigation patterns to advanced techniques like nested routes and programmatic navigation.
Understanding the Virtual DOM and Navigation
Before diving into routing, it's important to understand how euv's virtual DOM works. The virtual DOM in euv consists of several VirtualNode variants:
- Element: Represents an HTML element with attributes and children.
- Text: Represents a text node.
- Fragment: Represents a group of nodes without a wrapper element.
- Dynamic: Represents a node whose content can change reactively.
- Empty: Represents an empty or removed node.
When implementing routing, you're essentially swapping out parts of the virtual DOM based on the current route. The html! macro makes this straightforward with its support for conditional rendering:
html! {
div {
if current_route.get() == "home" {
home_page()
} else if current_route.get() == "about" {
about_page()
} else {
not_found_page()
}
}
}
Basic Navigation with Signals
The simplest approach to routing in euv uses a signal to track the current route. When the user navigates, the signal is updated, and the UI reactively renders the appropriate view:
let current_route: Signal<String> = use_signal(|| "home".to_string());
html! {
nav {
button {
onclick: move |_event: Event| {
current_route.set("home".to_string());
}
"Home"
}
button {
onclick: move |_event: Event| {
current_route.set("about".to_string());
}
"About"
}
}
div {
if current_route.get() == "home" {
h1 { "Home Page" }
} else if current_route.get() == "about" {
h1 { "About Page" }
}
}
}
This pattern uses the html! macro's if/else support to conditionally render different views based on the current route. When the current_route signal changes, the virtual DOM is updated automatically.
Using the Browser's History API
For a more robust navigation experience, you should integrate with the browser's History API. This ensures that the browser's back and forward buttons work correctly, and that the URL reflects the current view.
In euv, you can access browser APIs through the framework's browser integration layer. Here's how you might set up basic History API integration:
let current_route: Signal<String> = use_signal(|| "home".to_string());
// Listen for popstate events (back/forward button)
// Update current_route when the URL changes
button {
onclick: move |_event: Event| {
current_route.set("about".to_string());
// Push new state to browser history
}
"Go to About"
}
The key principle is to keep the signal in sync with the browser's URL. When the user clicks a navigation button, you update both the signal and the URL. When the user uses the back/forward button, you update the signal based on the URL change.
Component-Based Routing
As your application grows, you'll want to organize your routes into components. The #[component] macro makes it easy to create route components:
#[derive(Clone, Default)]
struct MyCardProps {
title: &'static str,
onclick: Option<Rc<dyn Fn(Event)>>,
disabled: Signal<bool>
}
#[component]
pub fn my_card(node: VirtualNode<MyCardProps>) -> VirtualNode {
let MyCardProps { title, .. } = node.try_get_props().unwrap_or_default();
let children: VirtualNode = node.try_get_child_node();
html! {
div {
h3 { title }
children
}
}
}
You can create specialized route components by passing route information through props. For example, a navigation link component might accept a route prop and handle navigation internally:
html! {
my_card {
title: "Navigation Card"
onclick: Rc::new(move |event: Event| {
current_route.set("dashboard".to_string());
})
button { "Go to Dashboard" }
}
}
The class! Macro for Active Route Styling
When building navigation menus, you typically want to highlight the currently active route. The class! macro in euv makes it easy to apply conditional CSS classes:
html! {
nav {
button {
class: if current_route.get() == "home" { "active" } else { "" }
onclick: move |_event: Event| {
current_route.set("home".to_string());
}
"Home"
}
button {
class: if current_route.get() == "about" { "active" } else { "" }
onclick: move |_event: Event| {
current_route.set("about".to_string());
}
"About"
}
}
}
This applies the "active" CSS class only to the button whose route matches the current route, providing visual feedback to the user about their current location.
Programmatic Navigation
Sometimes you need to navigate programmatically — for example, after a successful form submission or when an API call completes. In euv, this is as simple as updating the route signal:
let current_route: Signal<String> = use_signal(|| "home".to_string());
button {
onclick: move |_event: Event| {
// Perform some action (e.g., form submission)
let mut validation_errors: Vec<String> = Vec::new();
if username.get().trim().is_empty() {
validation_errors.push("Username is required".to_string());
}
if validation_errors.is_empty() {
errors.set("".to_string());
submitted.set(format!("Submitted: {}", username.get()));
// Navigate to success page
current_route.set("success".to_string());
} else {
errors.set(validation_errors.join("; "));
}
}
"Submit"
}
This pattern is useful for multi-step workflows, where you want to guide the user through a sequence of views.
Route Guards and Conditional Navigation
In many applications, you want to prevent users from accessing certain routes unless certain conditions are met (e.g., they're authenticated). In euv, you can implement route guards using the watch! macro:
watch!(current_route, |route: String| {
if route == "admin" && !is_authenticated.get() {
current_route.set("login".to_string());
}
});
This watcher checks every route change and redirects to the login page if the user tries to access the admin route without being authenticated.
Nested Routes
For complex applications with nested layouts, you can use a nested routing pattern. The parent component manages the top-level route, and child components manage their own sub-routes:
let current_route: Signal<String> = use_signal(|| "home".to_string());
html! {
div {
// Global navigation
nav {
button {
onclick: move |_event: Event| {
current_route.set("home".to_string());
}
"Home"
}
}
// Route-specific content
div {
if current_route.get() == "home" {
h1 { "Home Page" }
} else if current_route.get() == "dashboard" {
// Nested routing within the dashboard
dashboard_component { current_route: current_route }
}
}
}
}
URL-Based Routing
For a production-quality routing solution, you'll want to derive the current route from the browser's URL rather than maintaining it solely in a signal. This ensures that:
- Users can bookmark specific pages.
- The browser's URL always reflects the current view.
- Sharing links works correctly.
The approach involves reading the current URL on application startup, setting up listeners for URL changes, and updating the signal accordingly. The signal then drives the UI rendering through the virtual DOM.
Summary
Routing and navigation in euv are built on the framework's reactive signal system and virtual DOM. Key patterns include:
- Signal-based routing: Use a signal to track the current route and conditionally render views.
- History API integration: Sync the signal with the browser's URL for proper back/forward support.
-
Component-based routing: Organize routes into reusable components with the
#[component]macro. -
Active route styling: Use the
class!macro to highlight the current route in navigation menus. - Programmatic navigation: Update the route signal to navigate in response to application events.
-
Route guards: Use the
watch!macro to prevent unauthorized access to routes. - Nested routes: Manage sub-routes within child components for complex layouts.
By combining these patterns, you can build euv applications with robust, user-friendly navigation that provides a smooth single-page application experience.
Project Code:https://github.com/euv-dev/euv
Top comments (0)