3041 lines
150 KiB
HTML
3041 lines
150 KiB
HTML
<!DOCTYPE HTML>
|
|
<html lang="en" class="sidebar-visible no-js light">
|
|
<head>
|
|
<!-- Book generated using mdBook -->
|
|
<meta charset="UTF-8">
|
|
<title>Dioxus Router</title>
|
|
<meta name="robots" content="noindex" />
|
|
|
|
|
|
<!-- Custom HTML head -->
|
|
|
|
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
|
|
<meta name="description" content="">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<meta name="theme-color" content="#ffffff" />
|
|
|
|
<link rel="icon" href="favicon.svg">
|
|
<link rel="shortcut icon" href="favicon.png">
|
|
<link rel="stylesheet" href="css/variables.css">
|
|
<link rel="stylesheet" href="css/general.css">
|
|
<link rel="stylesheet" href="css/chrome.css">
|
|
<link rel="stylesheet" href="css/print.css" media="print">
|
|
|
|
<!-- Fonts -->
|
|
<link rel="stylesheet" href="FontAwesome/css/font-awesome.css">
|
|
<link rel="stylesheet" href="fonts/fonts.css">
|
|
|
|
<!-- Highlight.js Stylesheets -->
|
|
<link rel="stylesheet" href="highlight.css">
|
|
<link rel="stylesheet" href="tomorrow-night.css">
|
|
<link rel="stylesheet" href="ayu-highlight.css">
|
|
|
|
<!-- Custom theme stylesheets -->
|
|
|
|
</head>
|
|
<body>
|
|
<!-- Provide site root to javascript -->
|
|
<script type="text/javascript">
|
|
var path_to_root = "";
|
|
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "navy" : "light";
|
|
</script>
|
|
|
|
<!-- Work around some values being stored in localStorage wrapped in quotes -->
|
|
<script type="text/javascript">
|
|
try {
|
|
var theme = localStorage.getItem('mdbook-theme');
|
|
var sidebar = localStorage.getItem('mdbook-sidebar');
|
|
|
|
if (theme.startsWith('"') && theme.endsWith('"')) {
|
|
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
|
|
}
|
|
|
|
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
|
|
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
|
|
}
|
|
} catch (e) { }
|
|
</script>
|
|
|
|
<!-- Set the theme before any content is loaded, prevents flash -->
|
|
<script type="text/javascript">
|
|
var theme;
|
|
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
|
|
if (theme === null || theme === undefined) { theme = default_theme; }
|
|
var html = document.querySelector('html');
|
|
html.classList.remove('no-js')
|
|
html.classList.remove('light')
|
|
html.classList.add(theme);
|
|
html.classList.add('js');
|
|
</script>
|
|
|
|
<!-- Hide / unhide sidebar before it is displayed -->
|
|
<script type="text/javascript">
|
|
var html = document.querySelector('html');
|
|
var sidebar = 'hidden';
|
|
if (document.body.clientWidth >= 1080) {
|
|
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
|
|
sidebar = sidebar || 'visible';
|
|
}
|
|
html.classList.remove('sidebar-visible');
|
|
html.classList.add("sidebar-" + sidebar);
|
|
</script>
|
|
|
|
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
|
|
<div class="sidebar-scrollbox">
|
|
<ol class="chapter"><li class="chapter-item expanded affix "><a href="index.html">Introduction</a></li><li class="chapter-item expanded affix "><li class="part-title">Features</li><li class="chapter-item expanded "><a href="features/index.html"><strong aria-hidden="true">1.</strong> Adding the Router to Your Application</a></li><li class="chapter-item expanded "><a href="features/routes/index.html"><strong aria-hidden="true">2.</strong> Defining Routes</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="features/routes/nested.html"><strong aria-hidden="true">2.1.</strong> Nested Routes</a></li><li class="chapter-item expanded "><a href="features/routes/catch_all.html"><strong aria-hidden="true">2.2.</strong> Catch All Routes</a></li><li class="chapter-item expanded "><a href="features/routes/matching.html"><strong aria-hidden="true">2.3.</strong> Matching Routes</a></li><li class="chapter-item expanded "><a href="features/routes/fallback.html"><strong aria-hidden="true">2.4.</strong> Fallback Routes (404 page)</a></li><li class="chapter-item expanded "><a href="features/routes/multiple-and-redirect.html"><strong aria-hidden="true">2.5.</strong> Multiple Components & Redirects</a></li></ol></li><li class="chapter-item expanded "><a href="features/outlets.html"><strong aria-hidden="true">3.</strong> Outlets</a></li><li class="chapter-item expanded "><a href="features/navigation/index.html"><strong aria-hidden="true">4.</strong> Links & Navigation</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="features/navigation/name.html"><strong aria-hidden="true">4.1.</strong> Named Navigation</a></li><li class="chapter-item expanded "><a href="features/navigation/external.html"><strong aria-hidden="true">4.2.</strong> External Navigation</a></li><li class="chapter-item expanded "><a href="features/navigation/programmatic.html"><strong aria-hidden="true">4.3.</strong> Programmatic Navigation</a></li></ol></li><li class="chapter-item expanded "><a href="features/query.html"><strong aria-hidden="true">5.</strong> Query</a></li><li class="chapter-item expanded "><a href="features/failures/index.html"><strong aria-hidden="true">6.</strong> Navigation Failures</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="features/failures/named.html"><strong aria-hidden="true">6.1.</strong> Named Navigation Failure</a></li><li class="chapter-item expanded "><a href="features/failures/external.html"><strong aria-hidden="true">6.2.</strong> External Navigation Failure</a></li><li class="chapter-item expanded "><a href="features/failures/redirection-limit.html"><strong aria-hidden="true">6.3.</strong> Redirection Limit Failure</a></li></ol></li><li class="chapter-item expanded "><a href="features/history-providers.html"><strong aria-hidden="true">7.</strong> History Providers</a></li><li class="chapter-item expanded "><a href="features/history-buttons.html"><strong aria-hidden="true">8.</strong> History Buttons</a></li><li class="chapter-item expanded "><a href="features/sitemap-generation.html"><strong aria-hidden="true">9.</strong> Sitemap Generation</a></li><li class="chapter-item expanded "><a href="features/routing-update-callback.html"><strong aria-hidden="true">10.</strong> Routing Update Callback</a></li><li class="chapter-item expanded affix "><li class="part-title">Example Project</li><li class="chapter-item expanded "><a href="example/index.html"><strong aria-hidden="true">11.</strong> Overview</a></li><li class="chapter-item expanded "><a href="example/first-route.html"><strong aria-hidden="true">12.</strong> Creating Our First Route</a></li><li class="chapter-item expanded "><a href="example/building-a-nest.html"><strong aria-hidden="true">13.</strong> Building a Nest</a></li><li class="chapter-item expanded "><a href="example/navigation-targets.html"><strong aria-hidden="true">14.</strong> Navigation Targets</a></li><li class="chapter-item expanded "><a href="example/redirection-perfection.html"><strong aria-hidden="true">15.</strong> Redirection Perfection</a></li><li class="chapter-item expanded "><a href="example/full-code.html"><strong aria-hidden="true">16.</strong> Full Code</a></li></ol>
|
|
</div>
|
|
<div id="sidebar-resize-handle" class="sidebar-resize-handle"></div>
|
|
</nav>
|
|
|
|
<div id="page-wrapper" class="page-wrapper">
|
|
|
|
<div class="page">
|
|
<div id="menu-bar-hover-placeholder"></div>
|
|
<div id="menu-bar" class="menu-bar sticky bordered">
|
|
<div class="left-buttons">
|
|
<button id="sidebar-toggle" class="icon-button" type="button" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
|
|
<i class="fa fa-bars"></i>
|
|
</button>
|
|
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
|
|
<i class="fa fa-paint-brush"></i>
|
|
</button>
|
|
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
|
|
<li role="none"><button role="menuitem" class="theme" id="light">Light (default)</button></li>
|
|
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
|
|
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
|
|
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
|
|
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
|
|
</ul>
|
|
<button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
|
|
<i class="fa fa-search"></i>
|
|
</button>
|
|
</div>
|
|
|
|
<h1 class="menu-title">Dioxus Router</h1>
|
|
|
|
<div class="right-buttons">
|
|
<a href="print.html" title="Print this book" aria-label="Print this book">
|
|
<i id="print-button" class="fa fa-print"></i>
|
|
</a>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
<div id="search-wrapper" class="hidden">
|
|
<form id="searchbar-outer" class="searchbar-outer">
|
|
<input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
|
|
</form>
|
|
<div id="searchresults-outer" class="searchresults-outer hidden">
|
|
<div id="searchresults-header" class="searchresults-header"></div>
|
|
<ul id="searchresults">
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
|
|
<script type="text/javascript">
|
|
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
|
|
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
|
|
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
|
|
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
|
|
});
|
|
</script>
|
|
|
|
<div id="content" class="content">
|
|
<main>
|
|
<h1 id="introduction"><a class="header" href="#introduction">Introduction</a></h1>
|
|
<p>Whether or not you are building a website, desktop app, or mobile app,
|
|
splitting your app's views into "pages" can be an effective method for
|
|
organization and maintainability.</p>
|
|
<p>For this purpose, Dioxus provides a router. To start utilizing it, add it as a
|
|
dependency in your <code>Cargo.toml</code> file:</p>
|
|
<pre><code class="language-toml">[dependencies]
|
|
# use this for native apps
|
|
dioxus-router = "*"
|
|
#use this for web apps
|
|
dioxus-router = { version = "*", features = ["web"] }
|
|
|
|
# in both cases replace * with the current version
|
|
</code></pre>
|
|
<p>You can also use the <code>cargo</code> command to add the dependency:</p>
|
|
<pre><code class="language-sh">$ cargo add dioxus-router
|
|
$ cargo add dioxus-router --features web
|
|
</code></pre>
|
|
<blockquote>
|
|
<p>If you are not familiar with Dioxus itself, check out the <a href="https://dioxuslabs.com/guide/">Dioxus book</a>
|
|
first.</p>
|
|
</blockquote>
|
|
<p>This book is intended to get you up to speed with Dioxus Router. It is split
|
|
into two sections:</p>
|
|
<ol>
|
|
<li>The <a href="./features/index.html">Features</a> part explains individual features in
|
|
depth. You can read it start to finish, or you can read individual chapters
|
|
in whatever order you want.</li>
|
|
<li>If you prefer a learning-by-doing approach, you can check ouf the
|
|
<em><a href="./example/introduction.html">example project</a></em>. It guides you through
|
|
creating a dioxus app, setting up the router and using some of its
|
|
functionality.</li>
|
|
</ol>
|
|
<blockquote>
|
|
<p>Please note that this is not the only documentation for the Dioxus Router. You
|
|
can also check out the <a href="https://docs.rs/dioxus-router/">API Docs</a>.</p>
|
|
</blockquote>
|
|
<div style="break-before: page; page-break-before: always;"></div><h1 id="adding-the-router-to-your-application"><a class="header" href="#adding-the-router-to-your-application">Adding the Router to Your Application</a></h1>
|
|
<p>In this chapter we will learn how to add the router to our app. By it self, this
|
|
is not very useful. However, it is a prerequisite for all the functionality
|
|
described in the other chapters.</p>
|
|
<blockquote>
|
|
<p>Make sure you added the <code>dioxus-router</code> dependency as explained in the
|
|
<a href="features/../index.html">introduction</a>.</p>
|
|
</blockquote>
|
|
<p>In most cases we want to add the router to the root component of our app. This
|
|
way, we can ensure that we have access to all its functionality everywhere. We
|
|
add it by using the <a href="https://docs.rs/dioxus-router/latest/dioxus_router/hooks/fn.use_router.html"><code>use_router</code></a> hook</p>
|
|
<pre><pre class="playground"><code class="language-rust edition2021">
|
|
<span class="boring">#![allow(unused)]
|
|
</span><span class="boring">fn main() {
|
|
</span><span class="boring">// Hidden lines (like this one) make the documentation tests work.
|
|
</span><span class="boring">extern crate dioxus;
|
|
</span>use dioxus::prelude::*;
|
|
<span class="boring">extern crate dioxus_router;
|
|
</span>use dioxus_router::prelude::*;
|
|
<span class="boring">extern crate dioxus_ssr;
|
|
</span>
|
|
// This is the component we pass to dioxus when launching our app.
|
|
fn App(cx: Scope) -> Element {
|
|
// Here we add the router. All components inside `App` have access to its
|
|
// functionality.
|
|
let routes = use_router(
|
|
cx,
|
|
// The router can be configured with this parameter.
|
|
&|| RouterConfiguration {
|
|
<span class="boring"> synchronous: true,
|
|
</span> ..Default::default()
|
|
},
|
|
// This tells the router about all the routes in our application. As we
|
|
// don't have any, we pass an empty segment
|
|
&|| Segment::empty()
|
|
);
|
|
|
|
render! {
|
|
h1 { "Our sites title" }
|
|
|
|
// The Outlet tells the Router where to render active content.
|
|
Outlet { }
|
|
}
|
|
}
|
|
<span class="boring">
|
|
</span><span class="boring">let mut vdom = VirtualDom::new(App);
|
|
</span><span class="boring">let _ = vdom.rebuild();
|
|
</span><span class="boring">assert_eq!(
|
|
</span><span class="boring"> dioxus_ssr::render(&vdom),
|
|
</span><span class="boring"> "<h1>Our sites title</h1>"
|
|
</span><span class="boring">);
|
|
</span><span class="boring">}
|
|
</span></code></pre></pre>
|
|
<div style="break-before: page; page-break-before: always;"></div><h1 id="defining-routes"><a class="header" href="#defining-routes">Defining Routes</a></h1>
|
|
<p>When creating a router we need to pass it a <a href="https://docs.rs/dioxus-router-core/latest/dioxus_router_core/routes/struct.Segment.html"><code>Segment</code></a>. It tells the router
|
|
about all the routes of our app.</p>
|
|
<h2 id="example-content"><a class="header" href="#example-content">Example content</a></h2>
|
|
<p>To get a good understanding of how we define routes we first need to prepare
|
|
some example content, so we can see the routing in action.</p>
|
|
<pre><pre class="playground"><code class="language-rust no_run edition2021">
|
|
<span class="boring">#![allow(unused)]
|
|
</span><span class="boring">fn main() {
|
|
</span><span class="boring">// Hidden lines (like this one) make the documentation tests work.
|
|
</span><span class="boring">extern crate dioxus;
|
|
</span>use dioxus::prelude::*;
|
|
<span class="boring">extern crate dioxus_router;
|
|
</span>use dioxus_router::prelude::*;
|
|
|
|
fn Index(cx: Scope) -> Element {
|
|
render! {
|
|
h1 { "Welcome to our test site!" }
|
|
}
|
|
}
|
|
|
|
fn Other(cx: Scope) -> Element {
|
|
render! {
|
|
p { "some other content" }
|
|
}
|
|
}
|
|
<span class="boring">}
|
|
</span></code></pre></pre>
|
|
<h2 id="index-routes"><a class="header" href="#index-routes">Index routes</a></h2>
|
|
<p>The easiest thing to do is to define an index route.</p>
|
|
<p>Index routes act very similar to <code>index.html</code> files in most web servers. They
|
|
are active, when we don't specify a route.</p>
|
|
<blockquote>
|
|
<p>Note that we wrap our <code>Index</code> component with <a href="https://docs.rs/dioxus-router/latest/dioxus_router/prelude/fn.comp.html"><code>comp</code></a>. This is because of
|
|
rust type system requirements.</p>
|
|
</blockquote>
|
|
<pre><pre class="playground"><code class="language-rust no_run edition2021">
|
|
<span class="boring">#![allow(unused)]
|
|
</span><span class="boring">fn main() {
|
|
</span><span class="boring">// Hidden lines (like this one) make the documentation tests work.
|
|
</span><span class="boring">extern crate dioxus;
|
|
</span><span class="boring">use dioxus::prelude::*;
|
|
</span><span class="boring">extern crate dioxus_router;
|
|
</span><span class="boring">use dioxus_router::prelude::*;
|
|
</span><span class="boring">fn Index(cx: Scope) -> Element { unimplemented!() }
|
|
</span><span class="boring">
|
|
</span>fn App(cx: Scope) -> Element {
|
|
use_router(
|
|
cx,
|
|
&|| RouterConfiguration {
|
|
..Default::default()
|
|
},
|
|
&|| Segment::content(comp(Index))
|
|
);
|
|
|
|
// ...
|
|
<span class="boring"> unimplemented!()
|
|
</span>}
|
|
<span class="boring">}
|
|
</span></code></pre></pre>
|
|
<h2 id="fixed-routes"><a class="header" href="#fixed-routes">Fixed routes</a></h2>
|
|
<p>It is almost as easy to define a fixed route.</p>
|
|
<p>Fixed routes work similar to how web servers treat files. They are active, when
|
|
specified in the path. In the example, the path must be <code>/other</code>.</p>
|
|
<blockquote>
|
|
<p>The path will be URL decoded before checking if it matches our route.</p>
|
|
</blockquote>
|
|
<pre><pre class="playground"><code class="language-rust no_run edition2021">
|
|
<span class="boring">#![allow(unused)]
|
|
</span><span class="boring">fn main() {
|
|
</span><span class="boring">// Hidden lines (like this one) make the documentation tests work.
|
|
</span><span class="boring">extern crate dioxus;
|
|
</span><span class="boring">use dioxus::prelude::*;
|
|
</span><span class="boring">extern crate dioxus_router;
|
|
</span><span class="boring">use dioxus_router::prelude::*;
|
|
</span><span class="boring">fn Index(cx: Scope) -> Element { unimplemented!() }
|
|
</span><span class="boring">fn Other(cx: Scope) -> Element { unimplemented!() }
|
|
</span><span class="boring">
|
|
</span>fn App(cx: Scope) -> Element {
|
|
use_router(
|
|
cx,
|
|
&|| RouterConfiguration {
|
|
..Default::default()
|
|
},
|
|
&|| Segment::content(comp(Index)).fixed("other", comp(Other))
|
|
// ^ note the absence of a / prefix
|
|
);
|
|
|
|
// ...
|
|
<span class="boring"> unimplemented!()
|
|
</span>}
|
|
<span class="boring">}
|
|
</span></code></pre></pre>
|
|
<h2 id="full-code"><a class="header" href="#full-code">Full Code</a></h2>
|
|
<pre><pre class="playground"><code class="language-rust edition2021">
|
|
<span class="boring">#![allow(unused)]
|
|
</span><span class="boring">fn main() {
|
|
</span><span class="boring">// Hidden lines (like this one) make the documentation tests work.
|
|
</span><span class="boring">extern crate dioxus;
|
|
</span>use dioxus::prelude::*;
|
|
<span class="boring">extern crate dioxus_router;
|
|
</span>use dioxus_router::{history::MemoryHistory, prelude::*};
|
|
<span class="boring">extern crate dioxus_ssr;
|
|
</span>
|
|
fn Index(cx: Scope) -> Element {
|
|
render! {
|
|
h1 { "Welcome to our test site!" }
|
|
}
|
|
}
|
|
|
|
fn Other(cx: Scope) -> Element {
|
|
render! {
|
|
p { "some other content" }
|
|
}
|
|
}
|
|
|
|
fn App(cx: Scope) -> Element {
|
|
use_router(
|
|
cx,
|
|
&|| RouterConfiguration {
|
|
<span class="boring"> synchronous: true,
|
|
</span><span class="boring"> history: Box::new(MemoryHistory::with_initial_path("/other").unwrap()),
|
|
</span> ..Default::default()
|
|
},
|
|
&|| Segment::content(comp(Index)).fixed("other", comp(Other))
|
|
);
|
|
|
|
render! {
|
|
Outlet { }
|
|
}
|
|
}
|
|
<span class="boring">
|
|
</span><span class="boring">let mut vdom = VirtualDom::new(App);
|
|
</span><span class="boring">vdom.rebuild();
|
|
</span><span class="boring">assert_eq!(
|
|
</span><span class="boring"> dioxus_ssr::render(&vdom),
|
|
</span><span class="boring"> "<p>some other content</p>"
|
|
</span><span class="boring">);
|
|
</span><span class="boring">}
|
|
</span></code></pre></pre>
|
|
<div style="break-before: page; page-break-before: always;"></div><h1 id="nested-routes"><a class="header" href="#nested-routes">Nested Routes</a></h1>
|
|
<p>When developing bigger applications we often want to nest routes within each
|
|
other. As an example, we might want to organize a settings menu using this
|
|
pattern:</p>
|
|
<pre><code class="language-plain">└ Settings
|
|
├ General Settings (displayed when opening the settings)
|
|
├ Change Password
|
|
└ Privacy Settings
|
|
</code></pre>
|
|
<p>We might want to map this structure to these paths and components:</p>
|
|
<pre><code class="language-plain">/settings -> Settings { GeneralSettings }
|
|
/settings/password -> Settings { PWSettings }
|
|
/settings/privacy -> Settings { PrivacySettings }
|
|
</code></pre>
|
|
<p>Nested routes allow us to do this.</p>
|
|
<h2 id="route-depth"><a class="header" href="#route-depth">Route Depth</a></h2>
|
|
<p>With nesting routes, the router manages content on multiple levels. In our
|
|
example, when the path is <code>/settings</code>, there are two levels of content:</p>
|
|
<ol start="0">
|
|
<li>The <code>Settings</code> component</li>
|
|
<li>The <code>GeneralSettings</code> component</li>
|
|
</ol>
|
|
<p>Dioxus Router uses the <a href="https://docs.rs/dioxus-router/latest/dioxus_router/components/fn.Outlet.html"><code>Outlet</code></a> component to actually render content, but each
|
|
<a href="https://docs.rs/dioxus-router/latest/dioxus_router/components/fn.Outlet.html"><code>Outlet</code></a> can only render content from one level. This means that for the
|
|
content of nested routes to actually be rendered, we also need nested
|
|
<a href="https://docs.rs/dioxus-router/latest/dioxus_router/components/fn.Outlet.html"><code>Outlet</code></a>s.</p>
|
|
<h2 id="defining-the-content-components"><a class="header" href="#defining-the-content-components">Defining the content components</a></h2>
|
|
<p>We start by creating the components we want the router to render.</p>
|
|
<p>Take a look at the <code>Settings</code> component. When it gets rendered by an <a href="https://docs.rs/dioxus-router/latest/dioxus_router/components/fn.Outlet.html"><code>Outlet</code></a>,
|
|
it will render a second <a href="https://docs.rs/dioxus-router/latest/dioxus_router/components/fn.Outlet.html"><code>Outlet</code></a>. Thus the second <a href="https://docs.rs/dioxus-router/latest/dioxus_router/components/fn.Outlet.html"><code>Outlet</code></a> is nested within
|
|
the first one, and will in turn render our nested content.</p>
|
|
<pre><pre class="playground"><code class="language-rust no_run edition2021">
|
|
<span class="boring">#![allow(unused)]
|
|
</span><span class="boring">fn main() {
|
|
</span><span class="boring">// Hidden lines (like this one) make the documentation tests work.
|
|
</span><span class="boring">extern crate dioxus;
|
|
</span><span class="boring">use dioxus::prelude::*;
|
|
</span><span class="boring">extern crate dioxus_router;
|
|
</span><span class="boring">use dioxus_router::prelude::*;
|
|
</span><span class="boring">
|
|
</span>fn Settings(cx: Scope) -> Element {
|
|
render! {
|
|
h1 { "Settings" }
|
|
Outlet { }
|
|
}
|
|
}
|
|
|
|
fn GeneralSettings(cx: Scope) -> Element {
|
|
render! {
|
|
h2 { "General Settings" }
|
|
}
|
|
}
|
|
|
|
fn PWSettings(cx: Scope) -> Element {
|
|
render! {
|
|
h2 { "Password Settings" }
|
|
}
|
|
}
|
|
|
|
fn PrivacySettings(cx: Scope) -> Element {
|
|
render! {
|
|
h2 { "Privacy Settings" }
|
|
}
|
|
}
|
|
<span class="boring">}
|
|
</span></code></pre></pre>
|
|
<h2 id="defining-the-root-segment"><a class="header" href="#defining-the-root-segment">Defining the root <a href="https://docs.rs/dioxus-router-core/latest/dioxus_router_core/routes/struct.Segment.html"><code>Segment</code></a></a></h2>
|
|
<p>Now we create the <a href="https://docs.rs/dioxus-router-core/latest/dioxus_router_core/routes/struct.Segment.html"><code>Segment</code></a> that we will pass to the router.</p>
|
|
<p>Note that we wrap <code>comp(Settings)</code> within a <a href="https://docs.rs/dioxus-router-core/latest/dioxus_router_core/routes/struct.Route.html"><code>Route</code></a>. For this exact code that
|
|
is unnecessary, as this would be done automatically. However, in the next step
|
|
we'll use a method of <a href="https://docs.rs/dioxus-router-core/latest/dioxus_router_core/routes/struct.Route.html"><code>Route</code></a>, so we might as well add this now.</p>
|
|
<pre><pre class="playground"><code class="language-rust no_run edition2021">
|
|
<span class="boring">#![allow(unused)]
|
|
</span><span class="boring">fn main() {
|
|
</span><span class="boring">// Hidden lines (like this one) make the documentation tests work.
|
|
</span><span class="boring">extern crate dioxus;
|
|
</span><span class="boring">use dioxus::prelude::*;
|
|
</span><span class="boring">extern crate dioxus_router;
|
|
</span><span class="boring">use dioxus_router::prelude::*;
|
|
</span><span class="boring">fn Settings(cx: Scope) -> Element { unimplemented!() }
|
|
</span><span class="boring">
|
|
</span>fn App(cx: Scope) -> Element {
|
|
use_router(
|
|
cx,
|
|
&|| RouterConfiguration {
|
|
..Default::default()
|
|
},
|
|
&|| Segment::empty().fixed("settings", Route::content(comp(Settings)))
|
|
);
|
|
|
|
// ...
|
|
<span class="boring"> unimplemented!()
|
|
</span>}
|
|
<span class="boring">}
|
|
</span></code></pre></pre>
|
|
<h2 id="defining-the-nested-segment"><a class="header" href="#defining-the-nested-segment">Defining the nested <a href="https://docs.rs/dioxus-router-core/latest/dioxus_router_core/routes/struct.Segment.html"><code>Segment</code></a></a></h2>
|
|
<p>In order to create nested routes we need to create a nested <a href="https://docs.rs/dioxus-router-core/latest/dioxus_router_core/routes/struct.Segment.html"><code>Segment</code></a>. We then
|
|
pass it to the <a href="https://docs.rs/dioxus-router-core/latest/dioxus_router_core/routes/struct.Route.html"><code>Route</code></a> on the root segment.</p>
|
|
<blockquote>
|
|
<p>A <a href="https://docs.rs/dioxus-router-core/latest/dioxus_router_core/routes/struct.Segment.html"><code>Segment</code></a> always refers to one exact segment of the path.</p>
|
|
<p>https://router.example/<code>root_segment</code>/<code>first_nested_segment</code>/<code>second_nested_segment</code>/...</p>
|
|
</blockquote>
|
|
<pre><pre class="playground"><code class="language-rust no_run edition2021">
|
|
<span class="boring">#![allow(unused)]
|
|
</span><span class="boring">fn main() {
|
|
</span><span class="boring">// Hidden lines (like this one) make the documentation tests work.
|
|
</span><span class="boring">extern crate dioxus;
|
|
</span><span class="boring">use dioxus::prelude::*;
|
|
</span><span class="boring">extern crate dioxus_router;
|
|
</span><span class="boring">use dioxus_router::prelude::*;
|
|
</span><span class="boring">fn Settings(cx: Scope) -> Element { unimplemented!() }
|
|
</span><span class="boring">fn GeneralSettings(cx: Scope) -> Element { unimplemented!() }
|
|
</span><span class="boring">fn PWSettings(cx: Scope) -> Element { unimplemented!() }
|
|
</span><span class="boring">fn PrivacySettings(cx: Scope) -> Element { unimplemented!() }
|
|
</span><span class="boring">
|
|
</span>fn App(cx: Scope) -> Element {
|
|
use_router(
|
|
cx,
|
|
&|| RouterConfiguration {
|
|
..Default::default()
|
|
},
|
|
&|| Segment::empty().fixed(
|
|
"settings",
|
|
Route::content(comp(Settings)).nested(
|
|
Segment::content(comp(GeneralSettings))
|
|
.fixed("password", comp(PWSettings))
|
|
.fixed("privacy", comp(PrivacySettings))
|
|
)
|
|
)
|
|
);
|
|
|
|
// ...
|
|
<span class="boring"> unimplemented!()
|
|
</span>}
|
|
<span class="boring">}
|
|
</span></code></pre></pre>
|
|
<h2 id="full-code-1"><a class="header" href="#full-code-1">Full Code</a></h2>
|
|
<pre><pre class="playground"><code class="language-rust edition2021">
|
|
<span class="boring">#![allow(unused)]
|
|
</span><span class="boring">fn main() {
|
|
</span><span class="boring">// Hidden lines (like this one) make the documentation tests work.
|
|
</span><span class="boring">extern crate dioxus;
|
|
</span><span class="boring">use dioxus::prelude::*;
|
|
</span><span class="boring">extern crate dioxus_router;
|
|
</span><span class="boring">use dioxus_router::{history::MemoryHistory, prelude::*};
|
|
</span><span class="boring">extern crate dioxus_ssr;
|
|
</span><span class="boring">
|
|
</span>fn Settings(cx: Scope) -> Element {
|
|
render! {
|
|
h1 { "Settings" }
|
|
Outlet { }
|
|
}
|
|
}
|
|
|
|
fn GeneralSettings(cx: Scope) -> Element {
|
|
render! {
|
|
h2 { "General Settings" }
|
|
}
|
|
}
|
|
|
|
fn PWSettings(cx: Scope) -> Element {
|
|
render! {
|
|
h2 { "Password Settings" }
|
|
}
|
|
}
|
|
|
|
fn PrivacySettings(cx: Scope) -> Element {
|
|
render! {
|
|
h2 { "Privacy Settings" }
|
|
}
|
|
}
|
|
|
|
fn App(cx: Scope) -> Element {
|
|
use_router(
|
|
cx,
|
|
&|| RouterConfiguration {
|
|
<span class="boring"> synchronous: true,
|
|
</span><span class="boring"> history: Box::new(MemoryHistory::with_initial_path("/settings/privacy").unwrap()),
|
|
</span> ..Default::default()
|
|
},
|
|
&|| Segment::empty().fixed(
|
|
"settings",
|
|
Route::content(comp(Settings)).nested(
|
|
Segment::content(comp(GeneralSettings))
|
|
.fixed("password", comp(PWSettings))
|
|
.fixed("privacy", comp(PrivacySettings))
|
|
)
|
|
)
|
|
);
|
|
|
|
render! {
|
|
Outlet { }
|
|
}
|
|
}
|
|
<span class="boring">
|
|
</span><span class="boring">let mut vdom = VirtualDom::new(App);
|
|
</span><span class="boring">vdom.rebuild();
|
|
</span><span class="boring">assert_eq!(
|
|
</span><span class="boring"> dioxus_ssr::render(&vdom),
|
|
</span><span class="boring"> "<h1>Settings</h1><h2>Privacy Settings</h2>"
|
|
</span><span class="boring">);
|
|
</span><span class="boring">}
|
|
</span></code></pre></pre>
|
|
<div style="break-before: page; page-break-before: always;"></div><h1 id="catch-all-routes"><a class="header" href="#catch-all-routes">Catch All Routes</a></h1>
|
|
<p>Many modern web apps store parameters within their current path. This allows
|
|
users to share URLs that link to a specific bit of content. We can create this
|
|
functionality with catch all routes.</p>
|
|
<blockquote>
|
|
<p>If you want to change what route is active based on the format of the
|
|
parameter, see <a href="features/routes/./matching.html">Matching Routes</a>.</p>
|
|
</blockquote>
|
|
<blockquote>
|
|
<p>The parameter will be URL decoded.</p>
|
|
</blockquote>
|
|
<h2 id="creating-a-content-component"><a class="header" href="#creating-a-content-component">Creating a content component</a></h2>
|
|
<p>We start by creating a component that uses the parameters value.</p>
|
|
<p>We can get the current state of the router using the <a href="https://docs.rs/dioxus-router/latest/dioxus_router/hooks/fn.use_route.html"><code>use_route</code></a> hook. From
|
|
that state we can extract the current value of our parameter by using a key we
|
|
will later also define on our route.</p>
|
|
<blockquote>
|
|
<p>It is <strong>VERY IMPORTANT</strong> to drop the object returned by the <a href="https://docs.rs/dioxus-router/latest/dioxus_router/hooks/fn.use_route.html"><code>use_route</code></a>
|
|
hook once our component finished rendering. Otherwise the entire router will
|
|
be frozen.</p>
|
|
</blockquote>
|
|
<blockquote>
|
|
<p>The <a href="https://docs.rs/dioxus-router/latest/dioxus_router/hooks/fn.use_route.html"><code>use_route</code></a> hook can only be used in components nested within a
|
|
component that called <a href="https://docs.rs/dioxus-router/latest/dioxus_router/hooks/fn.use_router.html"><code>use_router</code></a>.</p>
|
|
</blockquote>
|
|
<pre><pre class="playground"><code class="language-rust no_run edition2021">
|
|
<span class="boring">#![allow(unused)]
|
|
</span><span class="boring">fn main() {
|
|
</span><span class="boring">// Hidden lines (like this one) make the documentation tests work.
|
|
</span><span class="boring">extern crate dioxus;
|
|
</span>use dioxus::prelude::*;
|
|
<span class="boring">extern crate dioxus_router;
|
|
</span>use dioxus_router::prelude::*;
|
|
|
|
struct Name;
|
|
|
|
fn Greeting(cx: Scope) -> Element {
|
|
let route = use_route(cx).expect("is nested within a Router component");
|
|
let name = route.parameter::<Name>()
|
|
.map(|name| name.clone())
|
|
.unwrap_or(String::from("world"));
|
|
|
|
render! {
|
|
p { "Hello, {name}!" }
|
|
}
|
|
}
|
|
<span class="boring">}
|
|
</span></code></pre></pre>
|
|
<h2 id="defining-the-routes"><a class="header" href="#defining-the-routes">Defining the routes</a></h2>
|
|
<p>Now we can define our route. Unlike a fixed <a href="https://docs.rs/dioxus-router-core/latest/dioxus_router_core/routes/struct.Route.html"><code>Route</code></a>, a <a href="https://docs.rs/dioxus-router-core/latest/dioxus_router_core/routes/struct.ParameterRoute.html"><code>ParameterRoute</code></a>
|
|
needs two arguments to be created.</p>
|
|
<blockquote>
|
|
<p>Also note that each <a href="https://docs.rs/dioxus-router-core/latest/dioxus_router_core/routes/struct.Segment.html"><code>Segment</code></a> can have exactly one parameter or
|
|
<a href="features/routes/./fallback.html">fallback route</a>.</p>
|
|
<p>For that reason, the example below would not work in practice, but showing
|
|
both forms (explicit and short) is more important for this example.</p>
|
|
</blockquote>
|
|
<pre><pre class="playground"><code class="language-rust no_run edition2021">
|
|
<span class="boring">#![allow(unused)]
|
|
</span><span class="boring">fn main() {
|
|
</span><span class="boring">// Hidden lines (like this one) make the documentation tests work.
|
|
</span><span class="boring">extern crate dioxus;
|
|
</span><span class="boring">use dioxus::prelude::*;
|
|
</span><span class="boring">extern crate dioxus_router;
|
|
</span><span class="boring">use dioxus_router::prelude::*;
|
|
</span><span class="boring">fn Greeting(cx: Scope) -> Element { unimplemented!() }
|
|
</span><span class="boring">
|
|
</span>struct Name;
|
|
|
|
fn App(cx: Scope) -> Element {
|
|
use_router(
|
|
cx,
|
|
&|| RouterConfiguration {
|
|
..Default::default()
|
|
},
|
|
&|| {
|
|
Segment::empty()
|
|
.catch_all(ParameterRoute::content::<Name>(comp(Greeting)))
|
|
.catch_all((comp(Greeting), Name { })) // same in short
|
|
}
|
|
);
|
|
|
|
// ...
|
|
<span class="boring"> unimplemented!()
|
|
</span>}
|
|
<span class="boring">}
|
|
</span></code></pre></pre>
|
|
<h2 id="interaction-with-other-routes"><a class="header" href="#interaction-with-other-routes">Interaction with other routes</a></h2>
|
|
<p>Each individual <a href="https://docs.rs/dioxus-router-core/latest/dioxus_router_core/routes/struct.Segment.html"><code>Segment</code></a> can only ever have one active route. This means that
|
|
when a <a href="https://docs.rs/dioxus-router-core/latest/dioxus_router_core/routes/struct.Segment.html"><code>Segment</code></a> has more than just a catch all route, the router has to
|
|
decide which is active. It does that this way:</p>
|
|
<ol start="0">
|
|
<li>If the segment is not specified (i.e. <code>/</code>), then the index route will be
|
|
active.</li>
|
|
<li>If a <a href="features/routes/./index.html#fixed-routes"><em>fixed</em></a> route matches the current path, it
|
|
will be active.</li>
|
|
<li>If a <a href="features/routes/./matching.html"><em>matching</em> route</a> matches the current path, it will be
|
|
active. <em>Matching</em> routes are checked in the order they are defined.</li>
|
|
<li>If neither a <em>fixed</em> nor a <em>matching</em> route is active, the <em>catch all</em> route
|
|
or <a href="features/routes/./fallback.html"><em>fallback</em> route</a> will be.</li>
|
|
</ol>
|
|
<p>Step 0 means that if we want a parameter to be empty, that needs to be specified
|
|
by the path, i.e. <code>//</code>.</p>
|
|
<blockquote>
|
|
<p>Be careful with using catch all routes on the root <a href="https://docs.rs/dioxus-router-core/latest/dioxus_router_core/routes/struct.Segment.html"><code>Segment</code></a>. Navigating to
|
|
paths starting with <code>//</code> will <strong>NOT</strong> work. This is not a limitation of the
|
|
router, but rather of how relative URLs work.</p>
|
|
<p>If you absolutely need an empty parameter on the root <a href="https://docs.rs/dioxus-router-core/latest/dioxus_router_core/routes/struct.Segment.html"><code>Segment</code></a>, a URL like
|
|
this <em>could</em> work:</p>
|
|
<ul>
|
|
<li><code>https://your-site.example//</code> for web sites</li>
|
|
<li><code>dioxus://index.html//</code> for desktop apps</li>
|
|
</ul>
|
|
</blockquote>
|
|
<h2 id="full-code-2"><a class="header" href="#full-code-2">Full Code</a></h2>
|
|
<pre><pre class="playground"><code class="language-rust edition2021">
|
|
<span class="boring">#![allow(unused)]
|
|
</span><span class="boring">fn main() {
|
|
</span><span class="boring">// Hidden lines (like this one) make the documentation tests work.
|
|
</span><span class="boring">extern crate dioxus;
|
|
</span><span class="boring">use dioxus::prelude::*;
|
|
</span><span class="boring">extern crate dioxus_router;
|
|
</span><span class="boring">use dioxus_router::{history::MemoryHistory, prelude::*};
|
|
</span><span class="boring">extern crate dioxus_ssr;
|
|
</span><span class="boring">
|
|
</span>struct Name;
|
|
|
|
fn Greeting(cx: Scope) -> Element {
|
|
let route = use_route(cx).expect("is nested within a Router component");
|
|
let name = route.parameter::<Name>()
|
|
.map(|name| name.clone())
|
|
.unwrap_or(String::from("world"));
|
|
|
|
render! {
|
|
p { "Hello, {name}!" }
|
|
}
|
|
}
|
|
|
|
fn App(cx: Scope) -> Element {
|
|
let routes = use_router(
|
|
cx,
|
|
&|| RouterConfiguration {
|
|
<span class="boring"> synchronous: true,
|
|
</span><span class="boring"> history: Box::new(MemoryHistory::with_initial_path("/Dioxus").unwrap()),
|
|
</span> ..Default::default()
|
|
},
|
|
&|| Segment::empty().catch_all((comp(Greeting), Name { }))
|
|
);
|
|
// ...
|
|
render! {
|
|
Outlet { }
|
|
}
|
|
}
|
|
<span class="boring">
|
|
</span><span class="boring">let mut vdom = VirtualDom::new(App);
|
|
</span><span class="boring">vdom.rebuild();
|
|
</span><span class="boring">assert_eq!(
|
|
</span><span class="boring"> dioxus_ssr::render(&vdom),
|
|
</span><span class="boring"> "<p>Hello, Dioxus!</p>"
|
|
</span><span class="boring">);
|
|
</span><span class="boring">}
|
|
</span></code></pre></pre>
|
|
<div style="break-before: page; page-break-before: always;"></div><h1 id="matching-routes"><a class="header" href="#matching-routes">Matching Routes</a></h1>
|
|
<blockquote>
|
|
<p>Make sure you understand how <a href="features/routes/./catch_all.html">catch all routes</a> work before
|
|
reading this page.</p>
|
|
</blockquote>
|
|
<p>When accepting parameters via the path, some complex applications might need to
|
|
decide what route should be active based on the format of that parameter.
|
|
<em>Matching</em> routes make it easy to implement such behavior.</p>
|
|
<blockquote>
|
|
<p>The parameter will be URL decoded, both for checking if the route is active
|
|
and when it is provided to the application.</p>
|
|
</blockquote>
|
|
<blockquote>
|
|
<p>The example below is only for showing <em>matching route</em> functionality. It is
|
|
unfit for all other purposes.</p>
|
|
</blockquote>
|
|
<h2 id="code-example"><a class="header" href="#code-example">Code Example</a></h2>
|
|
<blockquote>
|
|
<p>Notice that the parameter of a <em>matching route</em> has the same type as a
|
|
<a href="features/routes/./catch_all.html"><em>catch all route</em></a>.</p>
|
|
</blockquote>
|
|
<pre><pre class="playground"><code class="language-rust edition2021">
|
|
<span class="boring">#![allow(unused)]
|
|
</span><span class="boring">fn main() {
|
|
</span><span class="boring">// Hidden lines (like this one) make the documentation tests work.
|
|
</span><span class="boring">extern crate dioxus;
|
|
</span><span class="boring">use dioxus::prelude::*;
|
|
</span><span class="boring">extern crate dioxus_router;
|
|
</span><span class="boring">use dioxus_router::{history::MemoryHistory, prelude::*};
|
|
</span><span class="boring">extern crate dioxus_ssr;
|
|
</span><span class="boring">extern crate regex;
|
|
</span>use regex::Regex;
|
|
|
|
struct Name;
|
|
|
|
fn GreetingFemale(cx: Scope) -> Element {
|
|
let route = use_route(cx).unwrap();
|
|
let name = route.parameter::<Name>()
|
|
.map(|name| {
|
|
let mut name = name.to_string();
|
|
name.remove(0);
|
|
name
|
|
})
|
|
.unwrap_or(String::from("Anonymous"));
|
|
|
|
render! {
|
|
p { "Hello Mrs. {name}" }
|
|
}
|
|
}
|
|
|
|
fn GreetingMale(cx: Scope) -> Element {
|
|
let route = use_route(cx).unwrap();
|
|
let name = route.parameter::<Name>()
|
|
.map(|name| {
|
|
let mut name = name.to_string();
|
|
name.remove(0);
|
|
name
|
|
})
|
|
.unwrap_or(String::from("Anonymous"));
|
|
|
|
render! {
|
|
p { "Hello Mr. {name}" }
|
|
}
|
|
}
|
|
|
|
fn GreetingWithoutGender(cx: Scope) -> Element {
|
|
let route = use_route(cx).unwrap();
|
|
let name = route.parameter::<Name>()
|
|
.map(|name| name.to_string())
|
|
.unwrap_or(String::from("Anonymous"));
|
|
|
|
render! {
|
|
p { "Hello {name}" }
|
|
}
|
|
}
|
|
|
|
fn GreetingKenobi(cx: Scope) -> Element {
|
|
render! {
|
|
p { "Hello there." }
|
|
p { "General Kenobi." }
|
|
}
|
|
}
|
|
|
|
fn App(cx: Scope) -> Element {
|
|
use_router(
|
|
cx,
|
|
&|| RouterConfiguration {
|
|
<span class="boring"> synchronous: true,
|
|
</span><span class="boring"> history: Box::new(MemoryHistory::with_initial_path("/fAnna").unwrap()),
|
|
</span> ..Default::default()
|
|
},
|
|
&|| {
|
|
Segment::empty()
|
|
.fixed("kenobi", comp(GreetingKenobi))
|
|
.matching(
|
|
Regex::new("^f").unwrap(),
|
|
ParameterRoute::content::<Name>(comp(GreetingFemale))
|
|
)
|
|
.matching(
|
|
Regex::new("^m").unwrap(),
|
|
(comp(GreetingMale), Name { })
|
|
)
|
|
.catch_all((comp(GreetingWithoutGender), Name { }))
|
|
}
|
|
);
|
|
|
|
render! {
|
|
Outlet { }
|
|
}
|
|
}
|
|
<span class="boring">
|
|
</span><span class="boring">let mut vdom = VirtualDom::new(App);
|
|
</span><span class="boring">vdom.rebuild();
|
|
</span><span class="boring">let html = dioxus_ssr::render(&vdom);
|
|
</span><span class="boring">assert_eq!(html, "<p>Hello Mrs. Anna</p>");
|
|
</span><span class="boring">}
|
|
</span></code></pre></pre>
|
|
<h2 id="matcher"><a class="header" href="#matcher">Matcher</a></h2>
|
|
<p>In the example above, both <em>matching routes</em> use a regular expression to specify
|
|
when they match. However, <em>matching routes</em> are not limited to those. They
|
|
accept all types that implement the <a href="https://docs.rs/dioxus-router-core/latest/dioxus_router_core/routes/trait.Matcher.html"><code>Matcher</code></a> trait.</p>
|
|
<p>For example, you could (but probably shouldn't) implement a matcher, that
|
|
matches all values with an even number of characters:</p>
|
|
<pre><pre class="playground"><code class="language-rust no_run edition2021">
|
|
<span class="boring">#![allow(unused)]
|
|
</span><span class="boring">fn main() {
|
|
</span><span class="boring">// Hidden lines (like this one) make the documentation tests work.
|
|
</span><span class="boring">extern crate dioxus_router;
|
|
</span><span class="boring">use dioxus_router::prelude::*;
|
|
</span><span class="boring">
|
|
</span>#[derive(Debug)]
|
|
struct EvenMatcher;
|
|
|
|
impl Matcher for EvenMatcher {
|
|
fn matches(&self, value: &str) -> bool {
|
|
value.len() % 2 == 0
|
|
}
|
|
}
|
|
<span class="boring">}
|
|
</span></code></pre></pre>
|
|
<div style="break-before: page; page-break-before: always;"></div><h1 id="fallback-routes"><a class="header" href="#fallback-routes">Fallback Routes</a></h1>
|
|
<p>Sometimes the router might be unable to find a route for the provided path. We
|
|
might want it to show a prepared error message to our users in that case.
|
|
Fallback routes allow us to do that.</p>
|
|
<blockquote>
|
|
<p>This is especially important for use cases where users can manually change the
|
|
path, like web apps running in the browser.</p>
|
|
</blockquote>
|
|
<h2 id="a-single-global-fallback"><a class="header" href="#a-single-global-fallback">A single global fallback</a></h2>
|
|
<p>To catch all cases of invalid paths within our app, we can simply add a fallback
|
|
route to our root <a href="https://docs.rs/dioxus-router-core/latest/dioxus_router_core/routes/struct.Segment.html"><code>Segment</code></a>.</p>
|
|
<pre><pre class="playground"><code class="language-rust edition2021">
|
|
<span class="boring">#![allow(unused)]
|
|
</span><span class="boring">fn main() {
|
|
</span><span class="boring">// Hidden lines (like this one) make the documentation tests work.
|
|
</span><span class="boring">extern crate dioxus;
|
|
</span>use dioxus::prelude::*;
|
|
<span class="boring">extern crate dioxus_router;
|
|
</span>use dioxus_router::{history::MemoryHistory, prelude::*};
|
|
<span class="boring">extern crate dioxus_ssr;
|
|
</span>
|
|
fn Index(cx: Scope) -> Element {
|
|
render! {
|
|
h1 { "Index" }
|
|
}
|
|
}
|
|
|
|
fn Fallback(cx: Scope) -> Element {
|
|
render! {
|
|
h1 { "Error 404 - Not Found" }
|
|
p { "The page you asked for doesn't exist." }
|
|
}
|
|
}
|
|
|
|
fn App(cx: Scope) -> Element {
|
|
use_router(
|
|
cx,
|
|
&|| RouterConfiguration {
|
|
<span class="boring"> synchronous: true,
|
|
</span><span class="boring"> history: Box::new(MemoryHistory::with_initial_path("/invalid").unwrap()),
|
|
</span> ..Default::default()
|
|
},
|
|
&|| {
|
|
Segment::content(comp(Index)).fallback(comp(Fallback))
|
|
}
|
|
);
|
|
|
|
render! {
|
|
Outlet { }
|
|
}
|
|
}
|
|
<span class="boring">
|
|
</span><span class="boring">let mut vdom = VirtualDom::new(App);
|
|
</span><span class="boring">vdom.rebuild();
|
|
</span><span class="boring">assert_eq!(
|
|
</span><span class="boring"> dioxus_ssr::render(&vdom),
|
|
</span><span class="boring"> "<h1>Error 404 - Not Found</h1><p>The page you asked for doesn't exist.</p>"
|
|
</span><span class="boring">);
|
|
</span><span class="boring">}
|
|
</span></code></pre></pre>
|
|
<h2 id="more-specific-fallback-routes"><a class="header" href="#more-specific-fallback-routes">More specific fallback routes</a></h2>
|
|
<p>In some cases we might want to show different fallback content depending on what
|
|
section of our app the user is in.</p>
|
|
<p>For example, our app might have several settings pages under <code>/settings</code>, such
|
|
as the password settings <code>/settings/password</code> or the privacy settings
|
|
<code>/settings/privacy</code>. When our user is in the settings section, we want to show
|
|
them <em>"settings not found"</em> instead of <em>"page not found"</em>.</p>
|
|
<p>We can easily do that by setting a fallback route on our nested <a href="https://docs.rs/dioxus-router-core/latest/dioxus_router_core/routes/struct.Segment.html"><code>Segment</code></a>. It
|
|
will then replace the global fallback whenever our <a href="https://docs.rs/dioxus-router-core/latest/dioxus_router_core/routes/struct.Segment.html"><code>Segment</code></a> was active.</p>
|
|
<p>Note the <code>.clear_fallback(false)</code> part. If we didn't add this, the fallback
|
|
content would be rendered inside the <code>Settings</code> component.</p>
|
|
<pre><pre class="playground"><code class="language-rust edition2021">
|
|
<span class="boring">#![allow(unused)]
|
|
</span><span class="boring">fn main() {
|
|
</span><span class="boring">// Hidden lines (like this one) make the documentation tests work.
|
|
</span><span class="boring">extern crate dioxus;
|
|
</span>use dioxus::prelude::*;
|
|
<span class="boring">extern crate dioxus_router;
|
|
</span>use dioxus_router::{history::MemoryHistory, prelude::*};
|
|
<span class="boring">extern crate dioxus_ssr;
|
|
</span>
|
|
// This example doesn't show the index or settings components. It only shows how
|
|
// to set up several fallback routes.
|
|
<span class="boring">fn Index(cx: Scope) -> Element { unimplemented!() }
|
|
</span><span class="boring">fn Settings(cx: Scope) -> Element { unimplemented!() }
|
|
</span><span class="boring">fn GeneralSettings(cx: Scope) -> Element { unimplemented!() }
|
|
</span><span class="boring">fn PasswordSettings(cx: Scope) -> Element { unimplemented!() }
|
|
</span><span class="boring">fn PrivacySettings(cx: Scope) -> Element { unimplemented!() }
|
|
</span>
|
|
fn GlobalFallback(cx: Scope) -> Element {
|
|
render! {
|
|
h1 { "Error 404 - Page Not Found" }
|
|
}
|
|
}
|
|
|
|
fn SettingsFallback(cx: Scope) -> Element {
|
|
render! {
|
|
h1 { "Error 404 - Settings Not Found" }
|
|
}
|
|
}
|
|
|
|
fn App(cx: Scope) -> Element {
|
|
use_router(
|
|
cx,
|
|
&|| RouterConfiguration {
|
|
<span class="boring"> synchronous: true,
|
|
</span><span class="boring"> history: Box::new(MemoryHistory::with_initial_path("/settings/invalid").unwrap()),
|
|
</span> ..Default::default()
|
|
},
|
|
&|| {
|
|
Segment::empty()
|
|
.fixed("settings", Route::content(comp(Settings)).nested(
|
|
Segment::content(comp(GeneralSettings))
|
|
.fixed("password", comp(PasswordSettings))
|
|
.fixed("privacy", comp(PrivacySettings))
|
|
.fallback(comp(SettingsFallback))
|
|
.clear_fallback(true)
|
|
))
|
|
.fallback(comp(GlobalFallback))
|
|
}
|
|
);
|
|
|
|
render! {
|
|
Outlet { }
|
|
}
|
|
}
|
|
<span class="boring">
|
|
</span><span class="boring">let mut vdom = VirtualDom::new(App);
|
|
</span><span class="boring">vdom.rebuild();
|
|
</span><span class="boring">assert_eq!(
|
|
</span><span class="boring"> dioxus_ssr::render(&vdom),
|
|
</span><span class="boring"> "<h1>Error 404 - Settings Not Found</h1>"
|
|
</span><span class="boring">);
|
|
</span><span class="boring">}
|
|
</span></code></pre></pre>
|
|
<div style="break-before: page; page-break-before: always;"></div><h1 id="multiple-components--redirects"><a class="header" href="#multiple-components--redirects">Multiple Components & Redirects</a></h1>
|
|
<h2 id="multiple-components"><a class="header" href="#multiple-components">Multiple Components</a></h2>
|
|
<p>When creating complex apps we sometimes want to have multiple pieces of content
|
|
side by side. The router allows us to do this. For more details see the section
|
|
about <a href="features/routes/../outlets.html#named-outlets">named <code>Outlet</code>s</a>.</p>
|
|
<h2 id="redirects"><a class="header" href="#redirects">Redirects</a></h2>
|
|
<p>In some cases we may want to redirect our users to another page whenever they
|
|
open a specific path. We can tell the router to do this when defining our
|
|
routes.</p>
|
|
<blockquote>
|
|
<p>Redirects to external pages only work in certain conditions. For more details
|
|
see the chapter about <a href="features/routes/../failures/external.html">external navigation failures</a>.</p>
|
|
</blockquote>
|
|
<p>In the following example we will redirect everybody from <code>/</code> and <code>/start</code> to
|
|
<code>/home</code>.</p>
|
|
<pre><pre class="playground"><code class="language-rust edition2021">
|
|
<span class="boring">#![allow(unused)]
|
|
</span><span class="boring">fn main() {
|
|
</span><span class="boring">// Hidden lines (like this one) make the documentation tests work.
|
|
</span><span class="boring">extern crate dioxus;
|
|
</span>use dioxus::prelude::*;
|
|
<span class="boring">extern crate dioxus_router;
|
|
</span>use dioxus_router::{history::MemoryHistory, prelude::*};
|
|
<span class="boring">extern crate dioxus_ssr;
|
|
</span>
|
|
fn Home(cx: Scope) -> Element {
|
|
render! {
|
|
h1 { "Home Page" }
|
|
}
|
|
}
|
|
|
|
fn App(cx: Scope) -> Element {
|
|
use_router(
|
|
cx,
|
|
&|| RouterConfiguration {
|
|
<span class="boring"> synchronous: true,
|
|
</span><span class="boring"> history: Box::new(MemoryHistory::with_initial_path("/home").unwrap()),
|
|
</span> ..Default::default()
|
|
},
|
|
&|| {
|
|
Segment::content(comp(Home))
|
|
// notice that we use RouteContent::Redirect instead of
|
|
// RouteContent::Content (which we have been using indirectly)
|
|
.fixed(
|
|
"home",
|
|
RouteContent::Redirect(NavigationTarget::Internal("/".into()))
|
|
)
|
|
.fixed("start", "/") // short form
|
|
});
|
|
|
|
render! {
|
|
Outlet { }
|
|
}
|
|
}
|
|
<span class="boring">
|
|
</span><span class="boring">let mut vdom = VirtualDom::new(App);
|
|
</span><span class="boring">vdom.rebuild();
|
|
</span><span class="boring">let html = dioxus_ssr::render(&vdom);
|
|
</span><span class="boring">assert_eq!(html, "<h1>Home Page</h1>");
|
|
</span><span class="boring">}
|
|
</span></code></pre></pre>
|
|
<div style="break-before: page; page-break-before: always;"></div><h1 id="outlets"><a class="header" href="#outlets">Outlets</a></h1>
|
|
<p><a href="https://docs.rs/dioxus-router/latest/dioxus_router/components/fn.Outlet.html"><code>Outlet</code></a>s tell the router where to render content. In the following example
|
|
the active routes content will be rendered within the <a href="https://docs.rs/dioxus-router/latest/dioxus_router/components/fn.Outlet.html"><code>Outlet</code></a>.</p>
|
|
<pre><pre class="playground"><code class="language-rust edition2021">
|
|
<span class="boring">#![allow(unused)]
|
|
</span><span class="boring">fn main() {
|
|
</span><span class="boring">// Hidden lines (like this one) make the documentation tests work.
|
|
</span><span class="boring">extern crate dioxus;
|
|
</span><span class="boring">use dioxus::prelude::*;
|
|
</span><span class="boring">extern crate dioxus_router;
|
|
</span><span class="boring">use dioxus_router::prelude::*;
|
|
</span><span class="boring">extern crate dioxus_ssr;
|
|
</span>
|
|
fn Index(cx: Scope) -> Element {
|
|
render! {
|
|
h1 { "Index" }
|
|
}
|
|
}
|
|
|
|
fn App(cx: Scope) -> Element {
|
|
use_router(
|
|
cx,
|
|
&|| RouterConfiguration {
|
|
<span class="boring"> synchronous: true,
|
|
</span> ..Default::default()
|
|
},
|
|
&|| Segment::content(comp(Index))
|
|
);
|
|
|
|
render! {
|
|
header { "header" }
|
|
Outlet { }
|
|
footer { "footer" }
|
|
}
|
|
}
|
|
<span class="boring">
|
|
</span><span class="boring">let mut vdom = VirtualDom::new(App);
|
|
</span><span class="boring">vdom.rebuild();
|
|
</span><span class="boring">let html = dioxus_ssr::render(&vdom);
|
|
</span><span class="boring">assert_eq!(
|
|
</span><span class="boring"> html,
|
|
</span><span class="boring"> "<header>header</header><h1>Index</h1><footer>footer</footer>"
|
|
</span><span class="boring">);
|
|
</span><span class="boring">}
|
|
</span></code></pre></pre>
|
|
<p>The example above will output the following HTML (line breaks added for
|
|
readability):</p>
|
|
<pre><code class="language-html"><header>
|
|
header
|
|
</header>
|
|
<h1>
|
|
Index
|
|
</h1>
|
|
<footer>
|
|
footer
|
|
</footer>
|
|
</code></pre>
|
|
<h2 id="nested-outlets"><a class="header" href="#nested-outlets">Nested Outlets</a></h2>
|
|
<p>When using nested routes, we need to provide equally nested <a href="https://docs.rs/dioxus-router/latest/dioxus_router/components/fn.Outlet.html"><code>Outlet</code></a>s.</p>
|
|
<blockquote>
|
|
<p>Learn more about <a href="features/./routes/nested.html">nested routes</a> in their own chapter.</p>
|
|
</blockquote>
|
|
<h2 id="named-outlets"><a class="header" href="#named-outlets">Named Outlets</a></h2>
|
|
<p>When building complex apps, we often need to display multiple pieces of content
|
|
simultaneously. For example, we might have a sidebar that changes its content in
|
|
sync with the main part of the page.</p>
|
|
<p>When defining our routes, we can use <code>RouteContentMulti</code> instead of
|
|
<code>RouteContent::Component</code> (we've been using this through the <code>Into</code> trait) to
|
|
tell the router about our content.</p>
|
|
<p>We then can use a named <a href="https://docs.rs/dioxus-router/latest/dioxus_router/components/fn.Outlet.html"><code>Outlet</code></a> in our output, to tell the router where to
|
|
put the side content.</p>
|
|
<pre><pre class="playground"><code class="language-rust edition2021">
|
|
<span class="boring">#![allow(unused)]
|
|
</span><span class="boring">fn main() {
|
|
</span><span class="boring">// Hidden lines (like this one) make the documentation tests work.
|
|
</span><span class="boring">extern crate dioxus;
|
|
</span><span class="boring">use dioxus::prelude::*;
|
|
</span><span class="boring">extern crate dioxus_router;
|
|
</span><span class="boring">use dioxus_router::prelude::*;
|
|
</span><span class="boring">extern crate dioxus_ssr;
|
|
</span><span class="boring">
|
|
</span>
|
|
fn Main(cx: Scope) -> Element {
|
|
render! {
|
|
main { "Main Content" }
|
|
}
|
|
}
|
|
|
|
struct AsideName;
|
|
fn Aside(cx: Scope) -> Element {
|
|
render! {
|
|
aside { "Side Content" }
|
|
}
|
|
}
|
|
|
|
fn App(cx: Scope) -> Element {
|
|
use_router(
|
|
cx,
|
|
&|| RouterConfiguration {
|
|
<span class="boring"> synchronous: true,
|
|
</span> ..Default::default()
|
|
},
|
|
&|| {
|
|
Segment::content(
|
|
multi(Some(comp(Main)))
|
|
.add_named::<AsideName>(comp(Aside))
|
|
)
|
|
}
|
|
);
|
|
|
|
render! {
|
|
Outlet { }
|
|
Outlet {
|
|
name: Name::of::<AsideName>()
|
|
}
|
|
}
|
|
}
|
|
<span class="boring">
|
|
</span><span class="boring">let mut vdom = VirtualDom::new(App);
|
|
</span><span class="boring">vdom.rebuild();
|
|
</span><span class="boring">let html = dioxus_ssr::render(&vdom);
|
|
</span><span class="boring">assert_eq!(html, "<main>Main Content</main><aside>Side Content</aside>");
|
|
</span><span class="boring">}
|
|
</span></code></pre></pre>
|
|
<p>The example above will output the following HTML (line breaks added for
|
|
readability):</p>
|
|
<pre><code class="language-html"><main>
|
|
Main Content
|
|
</main>
|
|
<aside>
|
|
Side Content
|
|
</aside>
|
|
</code></pre>
|
|
<h2 id="outlet-depth-override"><a class="header" href="#outlet-depth-override">Outlet depth override</a></h2>
|
|
<p>When nesting <a href="https://docs.rs/dioxus-router/latest/dioxus_router/components/fn.Outlet.html"><code>Outlet</code></a>s, they communicate with each other. This allows the
|
|
nested <a href="https://docs.rs/dioxus-router/latest/dioxus_router/components/fn.Outlet.html"><code>Outlet</code></a> to render the content of the nested route.</p>
|
|
<p>We can override the detected value. Be careful when doing so, it is incredibly
|
|
easy to create an unterminated recursion. See below for an example of that.</p>
|
|
<pre><pre class="playground"><code class="language-rust edition2021">
|
|
<span class="boring">#![allow(unused)]
|
|
</span><span class="boring">fn main() {
|
|
</span><span class="boring">// Hidden lines (like this one) make the documentation tests work.
|
|
</span><span class="boring">extern crate dioxus;
|
|
</span><span class="boring">use dioxus::prelude::*;
|
|
</span><span class="boring">extern crate dioxus_router;
|
|
</span><span class="boring">use dioxus_router::{history::MemoryHistory, prelude::*};
|
|
</span><span class="boring">extern crate dioxus_ssr;
|
|
</span><span class="boring">
|
|
</span>fn RootContent(cx: Scope) -> Element {
|
|
render! {
|
|
h1 { "Root" }
|
|
Outlet { }
|
|
}
|
|
}
|
|
|
|
fn NestedContent(cx: Scope) -> Element {
|
|
render! {
|
|
h2 { "Nested" }
|
|
}
|
|
}
|
|
|
|
fn App(cx: Scope) -> Element {
|
|
use_router(
|
|
cx,
|
|
&|| RouterConfiguration {
|
|
<span class="boring"> synchronous: true,
|
|
</span><span class="boring"> history: Box::new(MemoryHistory::with_initial_path("/root").unwrap()),
|
|
</span> ..Default::default()
|
|
},
|
|
&|| {
|
|
Segment::empty().fixed(
|
|
"root",
|
|
Route::content(comp(RootContent)).nested(
|
|
Segment::content(comp(NestedContent))
|
|
)
|
|
)
|
|
}
|
|
);
|
|
|
|
render! {
|
|
Outlet {
|
|
depth: 1
|
|
}
|
|
}
|
|
}
|
|
<span class="boring">
|
|
</span><span class="boring">let mut vdom = VirtualDom::new(App);
|
|
</span><span class="boring">vdom.rebuild();
|
|
</span><span class="boring">let html = dioxus_ssr::render(&vdom);
|
|
</span><span class="boring">assert_eq!(html, "<h2>Nested</h2>");
|
|
</span><span class="boring">}
|
|
</span></code></pre></pre>
|
|
<p>The example above will output the following HTML (line breaks added for
|
|
readability):</p>
|
|
<pre><code class="language-html"><h2>
|
|
Nested
|
|
</h2>
|
|
</code></pre>
|
|
<h3 id="outlet-recursion"><a class="header" href="#outlet-recursion">Outlet recursion</a></h3>
|
|
<p>This code will create a crash due to an unterminated recursion using
|
|
<a href="https://docs.rs/dioxus-router/latest/dioxus_router/components/fn.Outlet.html"><code>Outlet</code></a>s.</p>
|
|
<pre><pre class="playground"><code class="language-rust no_run edition2021">
|
|
<span class="boring">#![allow(unused)]
|
|
</span><span class="boring">fn main() {
|
|
</span><span class="boring">// Hidden lines (like this one) make the documentation tests work.
|
|
</span><span class="boring">extern crate dioxus;
|
|
</span><span class="boring">use dioxus::prelude::*;
|
|
</span><span class="boring">extern crate dioxus_router;
|
|
</span><span class="boring">use dioxus_router::prelude::*;
|
|
</span><span class="boring">
|
|
</span>fn Content(cx: Scope) -> Element {
|
|
render! {
|
|
h1 { "Heyho!" }
|
|
Outlet {
|
|
depth: 0,
|
|
}
|
|
}
|
|
}
|
|
|
|
fn App(cx: Scope) -> Element {
|
|
use_router(cx, &Default::default, &|| Segment::content(comp(Content)));
|
|
|
|
render! {
|
|
Outlet { }
|
|
}
|
|
}
|
|
<span class="boring">}
|
|
</span></code></pre></pre>
|
|
<p>The <a href="https://docs.rs/dioxus-router/latest/dioxus_router/components/fn.Outlet.html"><code>Outlet</code></a> in the <code>App</code> component has no parent <a href="https://docs.rs/dioxus-router/latest/dioxus_router/components/fn.Outlet.html"><code>Outlet</code></a>, so its depth
|
|
will be <code>0</code>. When rendering for the path <code>/</code>, it therefore will render the
|
|
<code>Content</code> component.</p>
|
|
<p>The <code>Content</code> component will render an <code>h1</code> and an <a href="https://docs.rs/dioxus-router/latest/dioxus_router/components/fn.Outlet.html"><code>Outlet</code></a>. That <a href="https://docs.rs/dioxus-router/latest/dioxus_router/components/fn.Outlet.html"><code>OUtlet</code></a>
|
|
would usually have a depth of <code>1</code>, since it is a descendant of the <a href="https://docs.rs/dioxus-router/latest/dioxus_router/components/fn.Outlet.html"><code>Outlet</code></a> in
|
|
the <code>App</code> component. However, we override its depth to <code>0</code>, so it will render
|
|
the <code>Content</code> component.</p>
|
|
<p>That means the <code>Content</code> component will recurse until someone (e.g. the OS) puts
|
|
a stop to it.</p>
|
|
<div style="break-before: page; page-break-before: always;"></div><h1 id="links--navigation"><a class="header" href="#links--navigation">Links & Navigation</a></h1>
|
|
<p>When we split our app into pages, we need to provide our users with a way to
|
|
navigate between them. On regular web pages we'd use an anchor element for that,
|
|
like this:</p>
|
|
<pre><code class="language-html"><a href="/other">Link to an other page</a>
|
|
</code></pre>
|
|
<p>However, we cannot do that when using the router for two reasons:</p>
|
|
<ol>
|
|
<li>Anchor tags make the browser load a new page from the server. This takes a
|
|
lot of time, and it is much faster to let the router handle the navigation
|
|
client-side.</li>
|
|
<li>Navigation using anchor tags only works when the app is running inside a
|
|
browser. This means we cannot use them inside apps using Dioxus Desktop.</li>
|
|
</ol>
|
|
<p>To solve these problems, the router provides us with a <a href="https://docs.rs/dioxus-router/latest/dioxus_router/components/fn.Link.html"><code>Link</code></a> component we can
|
|
use like this:</p>
|
|
<pre><pre class="playground"><code class="language-rust no_run edition2021">
|
|
<span class="boring">#![allow(unused)]
|
|
</span><span class="boring">fn main() {
|
|
</span><span class="boring">// Hidden lines (like this one) make the documentation tests work.
|
|
</span><span class="boring">extern crate dioxus;
|
|
</span><span class="boring">use dioxus::prelude::*;
|
|
</span><span class="boring">extern crate dioxus_router;
|
|
</span><span class="boring">use dioxus_router::prelude::*;
|
|
</span>fn SomeComponent(cx: Scope) -> Element {
|
|
render! {
|
|
Link {
|
|
target: NavigationTarget::Internal(String::from("/some/path")),
|
|
"Link text"
|
|
}
|
|
Link {
|
|
target: "/some/path", // short form
|
|
"Other link text"
|
|
}
|
|
}
|
|
}
|
|
<span class="boring">}
|
|
</span></code></pre></pre>
|
|
<p>The <code>target</code> in the example above is similar to the <code>href</code> of a regular anchor
|
|
element. However, it tells the router more about what kind of navigation it
|
|
should perform:</p>
|
|
<ul>
|
|
<li>The example uses <a href="https://docs.rs/dioxus-router-core/latest/dioxus_router_core/navigation/enum.NavigationTarget.html#variant.Internal"><code>Internal</code></a>. We give it an arbitrary path that will be
|
|
merged with the current URL.</li>
|
|
<li><a href="https://docs.rs/dioxus-router-core/latest/dioxus_router_core/navigation/enum.NavigationTarget.html#variant.Named"><code>Named</code></a> allows us to navigate within our app using predefined names.
|
|
See the chapter about <a href="features/navigation/./name.html">named navigation</a> for more details.</li>
|
|
<li><a href="https://docs.rs/dioxus-router-core/latest/dioxus_router_core/navigation/enum.NavigationTarget.html#variant.External"><code>External</code></a> allows us to navigate to URLs outside of our app. See the
|
|
chapter about <a href="features/navigation/./external.html">external navigation</a> for more details.</li>
|
|
</ul>
|
|
<blockquote>
|
|
<p>The <a href="https://docs.rs/dioxus-router/latest/dioxus_router/components/fn.Link.html"><code>Link</code></a> accepts several props that modify its behavior. See the API docs
|
|
for more details.</p>
|
|
</blockquote>
|
|
<div style="break-before: page; page-break-before: always;"></div><h1 id="named-navigation"><a class="header" href="#named-navigation">Named Navigation</a></h1>
|
|
<p>When creating large applications, it can become difficult to keep track of all
|
|
routes and how to navigate to them. It also can be hard to find all links to
|
|
them, which makes it difficult to change paths.</p>
|
|
<p>To solve these problems, the router implements named navigation. When we define
|
|
our routes we can give them arbitrary, unique names (completely independent from
|
|
the path) and later ask the router to navigate to those names. The router will
|
|
automatically create the actual path to navigate to, even inserting required
|
|
parameters.</p>
|
|
<p><em>Named</em> navigation has a few advantages over <em>path-based</em> navigation:</p>
|
|
<ul>
|
|
<li>Links can be created without knowing the actual path.</li>
|
|
<li>It is much easier to find all links to a specific route.</li>
|
|
<li>The router knows what links are invalid (and will panic in debug builds).</li>
|
|
</ul>
|
|
<blockquote>
|
|
<p>When the router encounters an invalid link in a release build, it has to
|
|
handle that problem. You can hook into that process, to display a custom error
|
|
message. See the chapter about
|
|
<a href="features/navigation/../failures/named.html">named navigation failures</a>.</p>
|
|
</blockquote>
|
|
<blockquote>
|
|
<p>The router will automatically define the name <a href="https://docs.rs/dioxus-router-core/latest/dioxus_router_core/prelude/struct.RootIndex.html"><code>RootIndex</code></a> to refer to the
|
|
root index route (<code>/</code>).</p>
|
|
<p>It will also add other names (all of them are in the prelude module) in
|
|
certain conditions. None of these names can be used for app defined routes.</p>
|
|
</blockquote>
|
|
<h2 id="code-example-1"><a class="header" href="#code-example-1">Code Example</a></h2>
|
|
<pre><pre class="playground"><code class="language-rust edition2021">
|
|
<span class="boring">#![allow(unused)]
|
|
</span><span class="boring">fn main() {
|
|
</span><span class="boring">// Hidden lines (like this one) make the documentation tests work.
|
|
</span><span class="boring">extern crate dioxus;
|
|
</span>use dioxus::prelude::*;
|
|
<span class="boring">extern crate dioxus_router;
|
|
</span>use dioxus_router::prelude::*;
|
|
<span class="boring">extern crate dioxus_ssr;
|
|
</span>
|
|
// we define a unit struct which will serve as our name
|
|
struct TargetName;
|
|
|
|
fn Source(cx: Scope) -> Element {
|
|
render! {
|
|
Link {
|
|
// instead of InternalTarget we use NamedTarget (via the `named` fn)
|
|
// we can use the returned value to add parameters or a query
|
|
target: named::<TargetName>().query("query"),
|
|
"Go to target"
|
|
}
|
|
}
|
|
}
|
|
|
|
fn Target(cx: Scope) -> Element {
|
|
render! {
|
|
h1 { "Target" }
|
|
}
|
|
}
|
|
|
|
fn App(cx: Scope) -> Element {
|
|
use_router(
|
|
cx,
|
|
&|| RouterConfiguration {
|
|
<span class="boring"> synchronous: true,
|
|
</span> ..Default::default()
|
|
},
|
|
&|| {
|
|
Segment::content(comp(Source))
|
|
.fixed(
|
|
"target_path",
|
|
Route::content(comp(Target)).name::<TargetName>()
|
|
)
|
|
}
|
|
);
|
|
|
|
render! {
|
|
Outlet { }
|
|
}
|
|
}
|
|
<span class="boring">
|
|
</span><span class="boring">let mut vdom = VirtualDom::new(App);
|
|
</span><span class="boring">vdom.rebuild();
|
|
</span><span class="boring">let html = dioxus_ssr::render(&vdom);
|
|
</span><span class="boring">assert_eq!(
|
|
</span><span class="boring"> html,
|
|
</span><span class="boring"> format!(
|
|
</span><span class="boring"> "<a {attr1} {attr2}>Go to target</a>",
|
|
</span><span class="boring"> attr1 = r#"href="/target_path?query" dioxus-prevent-default="onclick""#,
|
|
</span><span class="boring"> attr2 = r#"class="" id="" rel="" target="""#
|
|
</span><span class="boring"> )
|
|
</span><span class="boring">)
|
|
</span><span class="boring">}
|
|
</span></code></pre></pre>
|
|
<h2 id="check-if-a-name-is-present"><a class="header" href="#check-if-a-name-is-present">Check if a name is present</a></h2>
|
|
<p>You can check if a specific name is present for the current route. This works
|
|
similar to getting the value of a <a href="features/navigation/../routes/parameter.html">parameter route</a> and
|
|
the same restrictions apply.</p>
|
|
<pre><pre class="playground"><code class="language-rust no_run edition2021">
|
|
<span class="boring">#![allow(unused)]
|
|
</span><span class="boring">fn main() {
|
|
</span><span class="boring">// Hidden lines (like this one) make the documentation tests work.
|
|
</span><span class="boring">extern crate dioxus;
|
|
</span>use dioxus::prelude::*;
|
|
<span class="boring">extern crate dioxus_router;
|
|
</span>use dioxus_router::prelude::*;
|
|
struct SomeName;
|
|
|
|
fn Content(cx: Scope) -> Element {
|
|
let route = use_route(cx).expect("needs to be in router");
|
|
|
|
if route.is_at(&named::<SomeName>(), false) {
|
|
// do something
|
|
}
|
|
|
|
// ...
|
|
<span class="boring"> todo!()
|
|
</span>}
|
|
<span class="boring">}
|
|
</span></code></pre></pre>
|
|
<div style="break-before: page; page-break-before: always;"></div><h1 id="external-navigation"><a class="header" href="#external-navigation">External Navigation</a></h1>
|
|
<p>In modern apps, and especially on the web, we often want to send our users to an
|
|
other website. <a href="https://docs.rs/dioxus-router-core/latest/dioxus_router_core/navigation/enum.NavigationTarget.html#variant.External"><code>External</code></a> allows us to make a <a href="https://docs.rs/dioxus-router/latest/dioxus_router/components/fn.Link.html"><code>Link</code></a> navigate to an
|
|
external page.</p>
|
|
<blockquote>
|
|
<p>You might already now about
|
|
<a href="features/navigation/../failures/external.html">external navigation failures</a>. The <a href="https://docs.rs/dioxus-router/latest/dioxus_router/components/fn.Link.html"><code>Link</code></a>
|
|
component doesn't rely on the code path where those originate. Therefore a
|
|
<a href="https://docs.rs/dioxus-router/latest/dioxus_router/components/fn.Link.html"><code>Link</code></a> will never trigger an external navigation failure.</p>
|
|
</blockquote>
|
|
<p>Strictly speaking, a <a href="https://docs.rs/dioxus-router/latest/dioxus_router/components/fn.Link.html"><code>Link</code></a> is not necessary for navigating to external
|
|
targets, since by definition the router cannot handle them internally. However,
|
|
the <a href="https://docs.rs/dioxus-router/latest/dioxus_router/components/fn.Link.html"><code>Link</code></a> component is more convenient to use, as it automatically sets the
|
|
<code>rel</code> attribute for the link, when the target is external.</p>
|
|
<h2 id="code-example-2"><a class="header" href="#code-example-2">Code Example</a></h2>
|
|
<pre><pre class="playground"><code class="language-rust edition2021">
|
|
<span class="boring">#![allow(unused)]
|
|
</span><span class="boring">fn main() {
|
|
</span><span class="boring">// Hidden lines (like this one) make the documentation tests work.
|
|
</span><span class="boring">extern crate dioxus;
|
|
</span>use dioxus::prelude::*;
|
|
<span class="boring">extern crate dioxus_router;
|
|
</span>use dioxus_router::prelude::*;
|
|
<span class="boring">extern crate dioxus_ssr;
|
|
</span>
|
|
fn App(cx: Scope) -> Element {
|
|
use_router(
|
|
cx,
|
|
&|| RouterConfiguration {
|
|
<span class="boring"> synchronous: true,
|
|
</span> ..Default::default()
|
|
},
|
|
&|| Segment::empty()
|
|
);
|
|
|
|
render! {
|
|
// links need to be inside a router, even if they navigate to an
|
|
// external page
|
|
Link {
|
|
target: NavigationTarget::External("https://dioxuslabs.com/".into()),
|
|
"Go to the dioxus home page"
|
|
}
|
|
Link {
|
|
target: "https://dioxuslabs.com/", // short form
|
|
"Go to the dioxus home page 2"
|
|
}
|
|
}
|
|
}
|
|
<span class="boring">
|
|
</span><span class="boring">let mut vdom = VirtualDom::new(App);
|
|
</span><span class="boring">vdom.rebuild();
|
|
</span><span class="boring">let html = dioxus_ssr::render(&vdom);
|
|
</span><span class="boring">assert_eq!(
|
|
</span><span class="boring"> html,
|
|
</span><span class="boring"> format!(
|
|
</span><span class="boring"> "<a {attr1} {attr2}>{text}</a><a {attr1} {attr2}>{text} 2</a>",
|
|
</span><span class="boring"> attr1 = r#"href="https://dioxuslabs.com/" dioxus-prevent-default="""#,
|
|
</span><span class="boring"> attr2 = r#"class="" id="" rel="noopener noreferrer" target="""#,
|
|
</span><span class="boring"> text = "Go to the dioxus home page"
|
|
</span><span class="boring"> )
|
|
</span><span class="boring">)
|
|
</span><span class="boring">}
|
|
</span></code></pre></pre>
|
|
<blockquote>
|
|
<p>Note that the short form for an [<code>ExternalTarget</code>] looks like the short form
|
|
for an [<code>InternalTarget</code>]. The router will create an [<code>ExternalTarget</code>] only
|
|
if the URL is absolute.</p>
|
|
</blockquote>
|
|
<div style="break-before: page; page-break-before: always;"></div><h1 id="programmatic-navigation"><a class="header" href="#programmatic-navigation">Programmatic Navigation</a></h1>
|
|
<p>Sometimes we want our application to navigate to another page without having the
|
|
user click on a link. This is called programmatic navigation.</p>
|
|
<h2 id="acquiring-a-navigator"><a class="header" href="#acquiring-a-navigator">Acquiring a <a href="https://docs.rs/dioxus-router-core/latest/dioxus_router_core/hooks/struct.Navigator.html"><code>Navigator</code></a></a></h2>
|
|
<p>To use programmatic navigation, we first have to acquire a <a href="https://docs.rs/dioxus-router-core/latest/dioxus_router_core/hooks/struct.Navigator.html"><code>Navigator</code></a>. For
|
|
that purpose we can use the <a href="https://docs.rs/dioxus-router/latest/dioxus_router/hooks/fn.use_navigate.html"><code>use_navigate</code></a> hook.</p>
|
|
<pre><pre class="playground"><code class="language-rust no_run edition2021">
|
|
<span class="boring">#![allow(unused)]
|
|
</span><span class="boring">fn main() {
|
|
</span><span class="boring">// Hidden lines (like this one) make the documentation tests work.
|
|
</span><span class="boring">extern crate dioxus;
|
|
</span>use dioxus::prelude::*;
|
|
<span class="boring">extern crate dioxus_router;
|
|
</span>use dioxus_router::prelude::*;
|
|
|
|
fn Content(cx: Scope) -> Element {
|
|
let nav = use_navigate(cx).expect("called inside a router");
|
|
|
|
// ...
|
|
<span class="boring"> unimplemented!()
|
|
</span>}
|
|
<span class="boring">}
|
|
</span></code></pre></pre>
|
|
<h2 id="triggering-a-navigation"><a class="header" href="#triggering-a-navigation">Triggering a Navigation</a></h2>
|
|
<p>We can use the <a href="https://docs.rs/dioxus-router-core/latest/dioxus_router_core/hooks/struct.Navigator.html"><code>Navigator</code></a> to trigger four different kinds of navigation:</p>
|
|
<ul>
|
|
<li><code>push</code> will navigate to the target. It works like a regular anchor tag.</li>
|
|
<li><code>replace</code> works like <code>push</code>, except that it replaces the current history entry
|
|
instead of adding a new one. This means the prior page cannot be restored with
|
|
the browsers back button.</li>
|
|
<li><code>Go back</code> works like the browsers back button.</li>
|
|
<li><code>Go forward</code> works like the browsers forward button (the opposite of the back
|
|
button).</li>
|
|
</ul>
|
|
<pre><pre class="playground"><code class="language-rust no_run edition2021">
|
|
<span class="boring">#![allow(unused)]
|
|
</span><span class="boring">fn main() {
|
|
</span><span class="boring">// Hidden lines (like this one) make the documentation tests work.
|
|
</span><span class="boring">extern crate dioxus;
|
|
</span><span class="boring">use dioxus::prelude::*;
|
|
</span><span class="boring">extern crate dioxus_router;
|
|
</span><span class="boring">use dioxus_router::prelude::*;
|
|
</span><span class="boring">
|
|
</span>fn Content(cx: Scope) -> Element {
|
|
let nav = use_navigate(cx).expect("called inside a router");
|
|
|
|
// push
|
|
nav.push("/target");
|
|
|
|
// replace
|
|
nav.replace("/target");
|
|
|
|
// go back
|
|
nav.go_back();
|
|
|
|
// go forward
|
|
nav.go_forward();
|
|
|
|
// ...
|
|
<span class="boring"> unimplemented!()
|
|
</span>}
|
|
<span class="boring">}
|
|
</span></code></pre></pre>
|
|
<p>You might have noticed that, like <a href="https://docs.rs/dioxus-router/latest/dioxus_router/components/fn.Link.html"><code>Link</code></a>, the <a href="https://docs.rs/dioxus-router-core/latest/dioxus_router_core/hooks/struct.Navigator.html"><code>Navigator</code></a>s <code>push</code> and
|
|
<code>replace</code> functions take a <a href="https://docs.rs/dioxus-router-core/latest/dioxus_router_core/navigation/enum.NavigationTarget.html"><code>NavigationTarget</code></a>. This means we can use
|
|
<a href="https://docs.rs/dioxus-router-core/latest/dioxus_router_core/navigation/enum.NavigationTarget.html#variant.Internal"><code>Internal</code></a>, <a href="https://docs.rs/dioxus-router-core/latest/dioxus_router_core/navigation/enum.NavigationTarget.html#variant.Named"><code>Named</code></a> and <a href="https://docs.rs/dioxus-router-core/latest/dioxus_router_core/navigation/enum.NavigationTarget.html#variant.External"><code>External</code></a>.</p>
|
|
<h2 id="external-navigation-targets"><a class="header" href="#external-navigation-targets">External Navigation Targets</a></h2>
|
|
<p>Unlike a <a href="https://docs.rs/dioxus-router/latest/dioxus_router/components/fn.Link.html"><code>Link</code></a>, the <a href="https://docs.rs/dioxus-router-core/latest/dioxus_router_core/hooks/struct.Navigator.html"><code>Navigator</code></a> cannot rely on the browser (or webview) to
|
|
handle navigation to external targets via a generated anchor element.</p>
|
|
<p>This means, that under certain conditions, navigation to external targets can
|
|
fail. See the chapter about
|
|
<a href="features/navigation/../failures/external.html">external navigation failures</a> for more details.</p>
|
|
<div style="break-before: page; page-break-before: always;"></div><h1 id="query"><a class="header" href="#query">Query</a></h1>
|
|
<p>Some apps use the query part of the URL to encode information. The router allows
|
|
you to easily access the query, as well as set it when navigating.</p>
|
|
<h2 id="accessing-the-query"><a class="header" href="#accessing-the-query">Accessing the query</a></h2>
|
|
<p>The <a href="https://docs.rs/dioxus-router/latest/dioxus_router/hooks/fn.use_route.html"><code>use_route</code></a> hook allows us to access the current query in two ways. The
|
|
returned <code>struct</code> contains a <code>query</code> field, that contains the query (without the
|
|
leading <code>?</code>).</p>
|
|
<pre><pre class="playground"><code class="language-rust no_run edition2021">
|
|
<span class="boring">#![allow(unused)]
|
|
</span><span class="boring">fn main() {
|
|
</span><span class="boring">// Hidden lines (like this one) make the documentation tests work.
|
|
</span><span class="boring">extern crate dioxus;
|
|
</span>use dioxus::prelude::*;
|
|
<span class="boring">extern crate dioxus_router;
|
|
</span>use dioxus_router::prelude::*;
|
|
|
|
fn SomeComponent(cx: Scope) -> Element {
|
|
let route = use_route(cx).expect("nested in Router");
|
|
|
|
let query = route.query.clone().unwrap();
|
|
|
|
// ...
|
|
<span class="boring"> unimplemented!()
|
|
</span>}
|
|
<span class="boring">}
|
|
</span></code></pre></pre>
|
|
<h2 id="setting-the-query"><a class="header" href="#setting-the-query">Setting the query</a></h2>
|
|
<p>When navigating we can tell the router to change the query. However, the method
|
|
we use to do this is very different, depending on how we specify our target.</p>
|
|
<h3 id="internal-and-external"><a class="header" href="#internal-and-external"><a href="https://docs.rs/dioxus-router-core/latest/dioxus_router_core/navigation/enum.NavigationTarget.html#variant.Internal"><code>Internal</code></a> and <a href="https://docs.rs/dioxus-router-core/latest/dioxus_router_core/navigation/enum.NavigationTarget.html#variant.External"><code>External</code></a></a></h3>
|
|
<p>When using <a href="https://docs.rs/dioxus-router-core/latest/dioxus_router_core/navigation/enum.NavigationTarget.html#variant.Internal"><code>Internal</code></a> or <a href="https://docs.rs/dioxus-router-core/latest/dioxus_router_core/navigation/enum.NavigationTarget.html#variant.External"><code>External</code></a> we have to append our query manually.</p>
|
|
<pre><pre class="playground"><code class="language-rust no_run edition2021">
|
|
<span class="boring">#![allow(unused)]
|
|
</span><span class="boring">fn main() {
|
|
</span><span class="boring">// Hidden lines (like this one) make the documentation tests work.
|
|
</span><span class="boring">extern crate dioxus;
|
|
</span><span class="boring">use dioxus::prelude::*;
|
|
</span><span class="boring">extern crate dioxus_router;
|
|
</span><span class="boring">use dioxus_router::prelude::*;
|
|
</span><span class="boring">
|
|
</span>fn SomeComponent(cx: Scope) -> Element {
|
|
render! {
|
|
Link {
|
|
target: NavigationTarget::Internal("/some/path?query=yes".into()),
|
|
"Internal target"
|
|
}
|
|
Link {
|
|
target: NavigationTarget::External("https://dioxuslab.com?query=yes".into()),
|
|
"External target"
|
|
}
|
|
}
|
|
}
|
|
<span class="boring">}
|
|
</span></code></pre></pre>
|
|
<h3 id="named"><a class="header" href="#named"><a href="https://docs.rs/dioxus-router-core/latest/dioxus_router_core/navigation/enum.NavigationTarget.html#variant.Named"><code>Named</code></a></a></h3>
|
|
<p>When using <a href="features/./navigation/name.html">named navigation</a> we can pass the query via
|
|
a function.</p>
|
|
<pre><pre class="playground"><code class="language-rust no_run edition2021">
|
|
<span class="boring">#![allow(unused)]
|
|
</span><span class="boring">fn main() {
|
|
</span><span class="boring">// Hidden lines (like this one) make the documentation tests work.
|
|
</span><span class="boring">extern crate dioxus;
|
|
</span><span class="boring">use dioxus::prelude::*;
|
|
</span><span class="boring">extern crate dioxus_router;
|
|
</span><span class="boring">use dioxus_router::prelude::*;
|
|
</span><span class="boring">struct Target;
|
|
</span><span class="boring">
|
|
</span>fn SomeComponent(cx: Scope) -> Element {
|
|
render! {
|
|
Link {
|
|
target: named::<Target>().query("query=yes"),
|
|
"Query String"
|
|
}
|
|
Link {
|
|
target: named::<Target>().query(vec![("query", "yes")]),
|
|
"Query Vec"
|
|
}
|
|
}
|
|
}
|
|
<span class="boring">}
|
|
</span></code></pre></pre>
|
|
<div style="break-before: page; page-break-before: always;"></div><h1 id="navigation-failures"><a class="header" href="#navigation-failures">Navigation Failures</a></h1>
|
|
<p>Some specific operations can cause a failure within router operations. The
|
|
subchapters contain information on how the router lets us handle such failures.</p>
|
|
<div style="break-before: page; page-break-before: always;"></div><h1 id="named-navigation-failure"><a class="header" href="#named-navigation-failure">Named Navigation Failure</a></h1>
|
|
<p>When using <a href="features/failures/../navigation/name.html">named navigation</a>, the router runs into a
|
|
problem under these circumstances:</p>
|
|
<ol>
|
|
<li>The name we try to navigate to is not contained within our routes.</li>
|
|
<li>The route we navigate to requires a parameter that we don't provide when
|
|
triggering the navigation.</li>
|
|
</ol>
|
|
<blockquote>
|
|
<p>Users cannot directly interact with named navigation. If a named navigation
|
|
failure occurs, your app (or the router) has a bug.</p>
|
|
</blockquote>
|
|
<p>The router reacts to this problem differently, depending on our apps build kind.</p>
|
|
<h2 id="debug"><a class="header" href="#debug">Debug</a></h2>
|
|
<p>When running a debug build, the router will <code>panic</code> whenever it encounters an
|
|
invalid navigation. This ensures that we notice these problems when we are
|
|
testing our application.</p>
|
|
<h2 id="release"><a class="header" href="#release">Release</a></h2>
|
|
<p>When running a release build, the router can't just <code>panic</code>, as that would be a
|
|
horrible user experience. Instead, it changes to show some fallback content.</p>
|
|
<blockquote>
|
|
<p>You can detect if the router is in the named navigation failure handling state
|
|
by <a href="features/failures/../navigation/name.html#check-if-a-name-is-present">checking</a> if the
|
|
<a href="https://docs.rs/dioxus-router-core/latest/dioxus_router_core/prelude/struct.FailureNamedNavigation.html"><code>FailureNamedNavigation</code></a> name is present.</p>
|
|
</blockquote>
|
|
<p>The default fallback explains to the user that an error occurred and asks them
|
|
to report the bug to the app developer.</p>
|
|
<p>You can override it by setting the <code>failure_named_navigation</code> value of the
|
|
<a href="https://docs.rs/dioxus-router/latest/dioxus_router/hooks/struct.RouterConfiguration.html"><code>RouterConfiguration</code></a>.</p>
|
|
<pre><pre class="playground"><code class="language-rust no_run edition2021">
|
|
<span class="boring">#![allow(unused)]
|
|
</span><span class="boring">fn main() {
|
|
</span><span class="boring">// Hidden lines (like this one) make the documentation tests work.
|
|
</span><span class="boring">extern crate dioxus;
|
|
</span><span class="boring">use dioxus::prelude::*;
|
|
</span><span class="boring">extern crate dioxus_router;
|
|
</span><span class="boring">use dioxus_router::prelude::*;
|
|
</span>fn NamedNavigationFallback(cx: Scope) -> Element {
|
|
render! {
|
|
h1 { "Named navigation failure!" }
|
|
}
|
|
}
|
|
|
|
fn App(cx: Scope) -> Element {
|
|
use_router(
|
|
cx,
|
|
&|| RouterConfiguration {
|
|
failure_named_navigation: comp(NamedNavigationFallback),
|
|
..Default::default()
|
|
},
|
|
&|| Segment::empty()
|
|
);
|
|
|
|
render! {
|
|
Outlet { }
|
|
}
|
|
}
|
|
<span class="boring">}
|
|
</span></code></pre></pre>
|
|
<div style="break-before: page; page-break-before: always;"></div><h1 id="external-navigation-failure"><a class="header" href="#external-navigation-failure">External Navigation Failure</a></h1>
|
|
<blockquote>
|
|
<p>This section doesn't apply when specifying a <code>target</code> on a <a href="https://docs.rs/dioxus-router/latest/dioxus_router/components/fn.Link.html"><code>Link</code></a>. See the
|
|
chapter about <a href="features/failures/../navigation/external.html">external navigation</a> for more
|
|
details.</p>
|
|
</blockquote>
|
|
<p>When we ask the router to navigate to an external target, either through
|
|
<a href="features/failures/../navigation/programmatic.html">programmatic navigation</a> or a
|
|
<a href="features/failures/../routes/multiple-and-redirect.html#redirects">redirect</a> the router needs to
|
|
navigate to an external target without being able to rely on an anchor element.</p>
|
|
<p>This will only work in the browser, when using either <a href="https://docs.rs/dioxus-router-core/latest/dioxus_router_core/history/struct.WebHistory.html"><code>WebHistory</code></a> or
|
|
<a href="https://docs.rs/dioxus-router-core/latest/dioxus_router_core/history/struct.WebHashHistory.html"><code>WebHashHistory</code></a>.</p>
|
|
<h2 id="failure-handling"><a class="header" href="#failure-handling">Failure handling</a></h2>
|
|
<p>When the router encounters an external navigation it cannot fulfill, it changes
|
|
the path to <code>/</code> and shows some fallback content.</p>
|
|
<blockquote>
|
|
<p>You can detect if the router is in the external navigation failure handling
|
|
state by <a href="features/failures/../navigation/name.html#check-if-a-name-is-present">checking</a> if the
|
|
<a href="https://docs.rs/dioxus-router-core/latest/dioxus_router_core/prelude/struct.FailureExternalNavigation.html"><code>FailureExternalNavigation</code></a> name is present.</p>
|
|
</blockquote>
|
|
<p>The default fallback explains to the user that the navigation was unsuccessful
|
|
and provides them with a <a href="https://docs.rs/dioxus-router/latest/dioxus_router/components/fn.Link.html"><code>Link</code></a> to fulfill it manually. It also allows them to
|
|
go back to the previous page.</p>
|
|
<p>You can override it by setting the <code>failure_external_navigation</code> value of the
|
|
<a href="https://docs.rs/dioxus-router/latest/dioxus_router/hooks/struct.RouterConfiguration.html"><code>RouterConfiguration</code></a>. The external URL will be provided via the
|
|
<a href="https://docs.rs/dioxus-router-core/latest/dioxus_router_core/prelude/struct.FailureExternalNavigation.html"><code>FailureExternalNavigation</code></a> parameter.</p>
|
|
<pre><pre class="playground"><code class="language-rust no_run edition2021">
|
|
<span class="boring">#![allow(unused)]
|
|
</span><span class="boring">fn main() {
|
|
</span><span class="boring">// Hidden lines (like this one) make the documentation tests work.
|
|
</span><span class="boring">extern crate dioxus;
|
|
</span><span class="boring">use dioxus::prelude::*;
|
|
</span><span class="boring">extern crate dioxus_router;
|
|
</span><span class="boring">use dioxus_router::prelude::*;
|
|
</span>fn ExternalNavigationFallback(cx: Scope) -> Element {
|
|
let route = use_route(cx).expect("is nested within a Router component");
|
|
let url = route
|
|
.parameter::<FailureExternalNavigation>()
|
|
.unwrap_or_default();
|
|
|
|
render! {
|
|
h1 { "External navigation failure!" }
|
|
Link {
|
|
target: url,
|
|
"Go to external site"
|
|
}
|
|
}
|
|
}
|
|
|
|
fn App(cx: Scope) -> Element {
|
|
use_router(
|
|
cx,
|
|
&|| RouterConfiguration {
|
|
failure_external_navigation: comp(ExternalNavigationFallback),
|
|
..Default::default()
|
|
},
|
|
&|| Segment::empty()
|
|
);
|
|
|
|
render! {
|
|
Outlet { }
|
|
}
|
|
}
|
|
<span class="boring">}
|
|
</span></code></pre></pre>
|
|
<div style="break-before: page; page-break-before: always;"></div><h1 id="redirection-limit-failure"><a class="header" href="#redirection-limit-failure">Redirection Limit Failure</a></h1>
|
|
<p>The router enforces a limit of 25 redirects during a single routing navigation.
|
|
This is done to prevent infinite loops. If your app breaches that limit, you
|
|
should reorganize its routes to reduce the number of redirects.</p>
|
|
<blockquote>
|
|
<p>Users cannot trigger a redirection. If the redirection limit is breached, your
|
|
app (or the router) has a bug.</p>
|
|
</blockquote>
|
|
<blockquote>
|
|
<p>The <a href="features/failures/../routing-update-callback.html"><code>on_update</code></a> callback doesn't count
|
|
towards the limit, and resets it. You may have 25 redirects, then add an other
|
|
one via the callback, and then have another 25.</p>
|
|
</blockquote>
|
|
<p>The router reacts to a breach differently, depending on our apps build kind.</p>
|
|
<h2 id="debug-1"><a class="header" href="#debug-1">Debug</a></h2>
|
|
<p>When running a debug build, the router will <code>panic</code> whenever the redirecion
|
|
limit is breached. This ensures that we notice these problems when we are
|
|
testing our application.</p>
|
|
<h2 id="release-1"><a class="header" href="#release-1">Release</a></h2>
|
|
<p>When running a release build, the router can't just <code>panic</code>, as that would be a
|
|
horrible user experience. Instead, it changes to show some fallback content.</p>
|
|
<blockquote>
|
|
<p>You can detect if the router is in the redirection limit failure handling
|
|
state by <a href="features/failures/../navigation/name.html#check-if-a-name-is-present">checking</a> if the
|
|
<a href="https://docs.rs/dioxus-router-core/latest/dioxus_router_core/prelude/struct.FailureRedirectionLimit.html"><code>FailureRedirectionLimit</code></a> name is present.</p>
|
|
</blockquote>
|
|
<p>The default fallback explains to the user that an error occurred and asks them
|
|
to report the bug to the app developer.</p>
|
|
<p>You can override it by setting the <code>failure_redirection_limit</code> value of the
|
|
<a href="https://docs.rs/dioxus-router/latest/dioxus_router/hooks/struct.RouterConfiguration.html"><code>RouterConfiguration</code></a>.</p>
|
|
<pre><pre class="playground"><code class="language-rust no_run edition2021">
|
|
<span class="boring">#![allow(unused)]
|
|
</span><span class="boring">fn main() {
|
|
</span><span class="boring">// Hidden lines (like this one) make the documentation tests work.
|
|
</span><span class="boring">extern crate dioxus;
|
|
</span><span class="boring">use dioxus::prelude::*;
|
|
</span><span class="boring">extern crate dioxus_router;
|
|
</span><span class="boring">use dioxus_router::prelude::*;
|
|
</span>fn RedirectionLimitFallback(cx: Scope) -> Element {
|
|
render! {
|
|
h1 { "Redirection limit breached!" }
|
|
}
|
|
}
|
|
|
|
fn App(cx: Scope) -> Element {
|
|
use_router(
|
|
cx,
|
|
&|| RouterConfiguration {
|
|
failure_redirection_limit: comp(RedirectionLimitFallback),
|
|
..Default::default()
|
|
},
|
|
&|| Segment::empty()
|
|
);
|
|
|
|
render! {
|
|
Outlet { }
|
|
}
|
|
}
|
|
<span class="boring">}
|
|
</span></code></pre></pre>
|
|
<div style="break-before: page; page-break-before: always;"></div><h1 id="history-providers"><a class="header" href="#history-providers">History Providers</a></h1>
|
|
<p>In order to provide the ability to traverse the navigation history, the router
|
|
uses <a href="https://docs.rs/dioxus-router-core/latest/dioxus_router_core/history/trait.HistoryProvider.html"><code>HistoryProvider</code></a>s. Those implement the actual back-and-forth
|
|
functionality.</p>
|
|
<p>The router provides five <a href="https://docs.rs/dioxus-router-core/latest/dioxus_router_core/history/trait.HistoryProvider.html"><code>HistoryProvider</code></a>s, but you can also create your own.
|
|
The five default implementations are:</p>
|
|
<ul>
|
|
<li>The <a href="https://docs.rs/dioxus-router-core/latest/dioxus_router_core/history/struct.MemoryHistory.html"><code>MemoryHistory</code></a> is a custom implementation that works in memory.</li>
|
|
<li>The <a href="https://docs.rs/dioxus-router-core/latest/dioxus_router_core/history/struct.WebHistory.html"><code>WebHistory</code></a> integrates with the browsers URL.</li>
|
|
<li>The <a href="https://docs.rs/dioxus-router-core/latest/dioxus_router_core/history/struct.WebHashHistory.html"><code>WebHashHistory</code></a> also integrates with the browser, but uses the fragment
|
|
part of the URL.</li>
|
|
</ul>
|
|
<p>By default the router uses the <a href="https://docs.rs/dioxus-router-core/latest/dioxus_router_core/history/struct.MemoryHistory.html"><code>MemoryHistory</code></a>. It might be changed to use
|
|
<a href="https://docs.rs/dioxus-router-core/latest/dioxus_router_core/history/struct.WebHistory.html"><code>WebHistory</code></a> when the <code>web</code> feature is active, but that is not guaranteed.</p>
|
|
<p>You can override the default history:</p>
|
|
<pre><pre class="playground"><code class="language-rust no_run edition2021">
|
|
<span class="boring">#![allow(unused)]
|
|
</span><span class="boring">fn main() {
|
|
</span><span class="boring">// Hidden lines (like this one) make the documentation tests work.
|
|
</span><span class="boring">extern crate dioxus;
|
|
</span>use dioxus::prelude::*;
|
|
<span class="boring">extern crate dioxus_router;
|
|
</span>use dioxus_router::{prelude::*, history::WebHashHistory};
|
|
|
|
fn App(cx: Scope) -> Element {
|
|
use_router(
|
|
cx,
|
|
&|| RouterConfiguration {
|
|
history: Box::new(WebHashHistory::new(true)),
|
|
..Default::default()
|
|
},
|
|
&|| Segment::empty()
|
|
);
|
|
|
|
render! {
|
|
Outlet { }
|
|
}
|
|
}
|
|
<span class="boring">}
|
|
</span></code></pre></pre>
|
|
<div style="break-before: page; page-break-before: always;"></div><h1 id="history-buttons"><a class="header" href="#history-buttons">History Buttons</a></h1>
|
|
<p>Some platforms, like web browsers, provide users with an easy way to navigate
|
|
through an apps history. They have UI elements or integrate with the OS.</p>
|
|
<p>However, native platforms usually don't provide such amenities, which means that
|
|
apps wanting users to have access to them, need to implement them. For this
|
|
reason the router comes with two components, which emulate a browsers back and
|
|
forward buttons:</p>
|
|
<ul>
|
|
<li><a href="https://docs.rs/dioxus-router/latest/dioxus_router/components/fn.GoBackButton.html"><code>GoBackButton</code></a></li>
|
|
<li><a href="https://docs.rs/dioxus-router/latest/dioxus_router/components/fn.GoForwardButton.html"><code>GoForwardButton</code></a></li>
|
|
</ul>
|
|
<blockquote>
|
|
<p>If you want to navigate through the history programmatically, take a look at
|
|
<a href="features/./navigation/programmatic.html"><code>programmatic navigation</code></a>.</p>
|
|
</blockquote>
|
|
<pre><pre class="playground"><code class="language-rust no_run edition2021">
|
|
<span class="boring">#![allow(unused)]
|
|
</span><span class="boring">fn main() {
|
|
</span><span class="boring">// Hidden lines (like this one) make the documentation tests work.
|
|
</span><span class="boring">extern crate dioxus;
|
|
</span>use dioxus::prelude::*;
|
|
<span class="boring">extern crate dioxus_router;
|
|
</span>use dioxus_router::prelude::*;
|
|
|
|
fn HistoryNavigation(cx: Scope) -> Element {
|
|
render! {
|
|
GoBackButton {
|
|
"Back to the Past"
|
|
}
|
|
GoForwardButton {
|
|
"Back to the Future" /* You see what I did there? 😉 */
|
|
}
|
|
}
|
|
}
|
|
<span class="boring">}
|
|
</span></code></pre></pre>
|
|
<p>As you might know, browsers usually disable the back and forward buttons if
|
|
there is no history to navigate to. The routers history buttons try to do that
|
|
too, but depending on the <a href="features/./history-providers.html">history provider</a> that might not be possible.</p>
|
|
<p>Importantly, neither <a href="https://docs.rs/dioxus-router-core/latest/dioxus_router_core/history/struct.WebHistory.html"><code>WebHistory</code></a> nor <a href="https://docs.rs/dioxus-router-core/latest/dioxus_router_core/history/struct.WebHashHistory.html"><code>WebHashHistory</code></a> support that feature.
|
|
This is due to limitations of the browser History API.</p>
|
|
<p>However, in both cases the router will just ignore button presses, if there is
|
|
no history to navigate to.</p>
|
|
<p>Also, when using <a href="https://docs.rs/dioxus-router-core/latest/dioxus_router_core/history/struct.WebHistory.html"><code>WebHistory</code></a> or <a href="https://docs.rs/dioxus-router-core/latest/dioxus_router_core/history/struct.WebHashHistory.html"><code>WebHashHistory</code></a>, the history buttons might
|
|
navigate a user to a history entry outside your app.</p>
|
|
<div style="break-before: page; page-break-before: always;"></div><h1 id="sitemap-generation"><a class="header" href="#sitemap-generation">Sitemap Generation</a></h1>
|
|
<p>If you need a list of all routes you have defined (e.g. for statically
|
|
generating all pages), Dioxus Router provides functions to extract that
|
|
information from a <a href="https://docs.rs/dioxus-router-core/latest/dioxus_router_core/routes/struct.Segment.html"><code>Segment</code></a>.</p>
|
|
<h2 id="preparing-an-app"><a class="header" href="#preparing-an-app">Preparing an app</a></h2>
|
|
<p>We will start by preparing an app with some routes like we normally would.</p>
|
|
<pre><pre class="playground"><code class="language-rust edition2021">
|
|
<span class="boring">#![allow(unused)]
|
|
</span><span class="boring">fn main() {
|
|
</span><span class="boring">// Hidden lines (like this one) make the documentation tests work.
|
|
</span><span class="boring">extern crate dioxus;
|
|
</span>use dioxus::prelude::*;
|
|
<span class="boring">extern crate dioxus_router;
|
|
</span>use dioxus_router::{history::MemoryHistory, prelude::*};
|
|
<span class="boring">extern crate dioxus_ssr;
|
|
</span>
|
|
fn Home(cx: Scope) -> Element {
|
|
render! {
|
|
h1 { "Home" }
|
|
}
|
|
}
|
|
|
|
fn Fixed(cx: Scope) -> Element {
|
|
render! {
|
|
h1 { "Fixed" }
|
|
Outlet { }
|
|
}
|
|
}
|
|
|
|
fn Nested(cx: Scope) -> Element {
|
|
render! {
|
|
h2 { "Nested" }
|
|
}
|
|
}
|
|
|
|
struct ParameterName;
|
|
fn Parameter(cx: Scope) -> Element {
|
|
let route = use_route(cx).unwrap();
|
|
let param = route.parameter::<ParameterName>().unwrap_or_default();
|
|
|
|
render! {
|
|
h1 { "Parameter: {param}" }
|
|
}
|
|
}
|
|
|
|
fn App(cx: Scope) -> Element {
|
|
use_router(
|
|
cx,
|
|
&|| RouterConfiguration {
|
|
<span class="boring"> synchronous: true,
|
|
</span> history: Box::new(MemoryHistory::with_initial_path("/fixed/nested").unwrap()),
|
|
..Default::default()
|
|
},
|
|
&|| {
|
|
Segment::content(comp(Home))
|
|
.fixed(
|
|
"fixed",
|
|
Route::content(comp(Fixed)).nested(
|
|
Segment::empty().fixed("nested", comp(Nested))
|
|
)
|
|
)
|
|
.catch_all((comp(Parameter), ParameterName { }))
|
|
}
|
|
);
|
|
|
|
render! {
|
|
Outlet { }
|
|
}
|
|
}
|
|
<span class="boring">
|
|
</span><span class="boring">let mut vdom = VirtualDom::new(App);
|
|
</span><span class="boring">vdom.rebuild();
|
|
</span><span class="boring">assert_eq!(dioxus_ssr::render(&mut vdom), "<h1>Fixed</h1><h2>Nested</h2>");
|
|
</span><span class="boring">}
|
|
</span></code></pre></pre>
|
|
<h2 id="modifying-the-app-to-make-using-sitemaps-easier"><a class="header" href="#modifying-the-app-to-make-using-sitemaps-easier">Modifying the app to make using sitemaps easier</a></h2>
|
|
<p>Preparing our app for sitemap generation is quite easy. We just need to extract
|
|
our segment definition into its own function.</p>
|
|
<pre><pre class="playground"><code class="language-rust no_run edition2021">
|
|
<span class="boring">#![allow(unused)]
|
|
</span><span class="boring">fn main() {
|
|
</span><span class="boring">// Hidden lines (like this one) make the documentation tests work.
|
|
</span><span class="boring">extern crate dioxus;
|
|
</span><span class="boring">use dioxus::prelude::*;
|
|
</span><span class="boring">extern crate dioxus_router;
|
|
</span><span class="boring">use dioxus_router::prelude::*;
|
|
</span><span class="boring">extern crate dioxus_ssr;
|
|
</span><span class="boring">fn Home(cx: Scope) -> Element { unimplemented!() }
|
|
</span><span class="boring">fn Fixed(cx: Scope) -> Element { unimplemented!() }
|
|
</span><span class="boring">fn Nested(cx: Scope) -> Element { unimplemented!() }
|
|
</span><span class="boring">struct ParameterName;
|
|
</span><span class="boring">fn Parameter(cx: Scope) -> Element { unimplemented!() }
|
|
</span><span class="boring">
|
|
</span>fn App(cx: Scope) -> Element {
|
|
use_router(
|
|
cx,
|
|
&|| RouterConfiguration {
|
|
..Default::default()
|
|
},
|
|
&prepare_routes
|
|
);
|
|
|
|
render! {
|
|
Outlet { }
|
|
}
|
|
}
|
|
|
|
fn prepare_routes() -> Segment<Component> {
|
|
Segment::content(comp(Home))
|
|
.fixed(
|
|
"fixed",
|
|
Route::content(comp(Fixed)).nested(
|
|
Segment::empty().fixed("nested", comp(Nested))
|
|
)
|
|
)
|
|
.catch_all((comp(Parameter), ParameterName { }))
|
|
}
|
|
<span class="boring">}
|
|
</span></code></pre></pre>
|
|
<h2 id="sitemaps-with-parameter-names"><a class="header" href="#sitemaps-with-parameter-names">Sitemaps with parameter names</a></h2>
|
|
<p>The first variant to generate sitemaps is very simple. It finds all routes
|
|
within the <a href="https://docs.rs/dioxus-router-core/latest/dioxus_router_core/routes/struct.Segment.html"><code>Segment</code></a> and adds them to the returned <code>Vec</code>.</p>
|
|
<p>Matching and parameter routes are represented by their <code>key</code>, prefixed with <code>\</code>.
|
|
Besides that <code>\</code>, all paths are URL encoded.</p>
|
|
<pre><pre class="playground"><code class="language-rust edition2021">
|
|
<span class="boring">#![allow(unused)]
|
|
</span><span class="boring">fn main() {
|
|
</span><span class="boring">// Hidden lines (like this one) make the documentation tests work.
|
|
</span><span class="boring">extern crate dioxus;
|
|
</span><span class="boring">use dioxus::prelude::*;
|
|
</span><span class="boring">extern crate dioxus_router;
|
|
</span><span class="boring">use dioxus_router::prelude::*;
|
|
</span><span class="boring">extern crate dioxus_ssr;
|
|
</span><span class="boring">fn Home(cx: Scope) -> Element { unimplemented!() }
|
|
</span><span class="boring">fn Fixed(cx: Scope) -> Element { unimplemented!() }
|
|
</span><span class="boring">fn Nested(cx: Scope) -> Element { unimplemented!() }
|
|
</span><span class="boring">struct ParameterName;
|
|
</span><span class="boring">fn Parameter(cx: Scope) -> Element { unimplemented!() }
|
|
</span><span class="boring">fn prepare_routes() -> Segment<Component> {
|
|
</span><span class="boring"> Segment::content(comp(Home))
|
|
</span><span class="boring"> .fixed(
|
|
</span><span class="boring"> "fixed",
|
|
</span><span class="boring"> Route::content(comp(Fixed)).nested(
|
|
</span><span class="boring"> Segment::empty().fixed("nested", comp(Nested))
|
|
</span><span class="boring"> )
|
|
</span><span class="boring"> )
|
|
</span><span class="boring"> .catch_all((comp(Parameter), ParameterName { }))
|
|
</span><span class="boring">}
|
|
</span>
|
|
let expected = vec![
|
|
"/",
|
|
"/fixed",
|
|
"/fixed/nested",
|
|
// Usually, here would be a fourth result representing the parameter route.
|
|
// However, due to mdbook the name for this file would constantly change,
|
|
// which is why we cannot show it. It would look something like this:
|
|
// "/\\your_crate::ParameterName",
|
|
];
|
|
let mut sitemap = prepare_routes().gen_sitemap();
|
|
sitemap.remove(3); // see above
|
|
assert_eq!(sitemap, expected);
|
|
<span class="boring">}
|
|
</span></code></pre></pre>
|
|
<h2 id="sitemaps-with-actual-parameter-values"><a class="header" href="#sitemaps-with-actual-parameter-values">Sitemaps with actual parameter values</a></h2>
|
|
<p>The second variant to generate sitemaps is a bit more involved. When it
|
|
encounters a parameter route, it inserts all values with a matching <code>key</code> that
|
|
were provided to it.</p>
|
|
<p>Matching routes only add their path if the value matches their regex.</p>
|
|
<p>All paths are URL encoded.</p>
|
|
<pre><pre class="playground"><code class="language-rust edition2021">
|
|
<span class="boring">#![allow(unused)]
|
|
</span><span class="boring">fn main() {
|
|
</span><span class="boring">// Hidden lines (like this one) make the documentation tests work.
|
|
</span>use std::collections::{BTreeMap, HashSet};
|
|
<span class="boring">extern crate dioxus;
|
|
</span><span class="boring">use dioxus::prelude::*;
|
|
</span><span class="boring">extern crate dioxus_router;
|
|
</span><span class="boring">use dioxus_router::prelude::*;
|
|
</span><span class="boring">extern crate dioxus_ssr;
|
|
</span><span class="boring">fn Home(cx: Scope) -> Element { unimplemented!() }
|
|
</span><span class="boring">fn Fixed(cx: Scope) -> Element { unimplemented!() }
|
|
</span><span class="boring">fn Nested(cx: Scope) -> Element { unimplemented!() }
|
|
</span><span class="boring">struct ParameterName;
|
|
</span><span class="boring">fn Parameter(cx: Scope) -> Element { unimplemented!() }
|
|
</span><span class="boring">fn prepare_routes() -> Segment<Component> {
|
|
</span><span class="boring"> Segment::content(comp(Home))
|
|
</span><span class="boring"> .fixed(
|
|
</span><span class="boring"> "fixed",
|
|
</span><span class="boring"> Route::content(comp(Fixed)).nested(
|
|
</span><span class="boring"> Segment::empty().fixed("nested", comp(Nested))
|
|
</span><span class="boring"> )
|
|
</span><span class="boring"> )
|
|
</span><span class="boring"> .catch_all((comp(Parameter), ParameterName { }))
|
|
</span><span class="boring">}
|
|
</span>
|
|
let parameters = {
|
|
let mut parameters = BTreeMap::new();
|
|
|
|
parameters.insert(
|
|
Name::of::<ParameterName>(),
|
|
vec![
|
|
String::from("some-parameter-value"),
|
|
String::from("other-parameter-value")
|
|
]
|
|
);
|
|
|
|
parameters
|
|
};
|
|
|
|
let expected: Vec<String> = vec![
|
|
"/",
|
|
"/fixed",
|
|
"/fixed/nested",
|
|
"/some-parameter-value",
|
|
"/other-parameter-value",
|
|
].into_iter().map(String::from).collect();
|
|
assert_eq!(expected, prepare_routes().gen_parameter_sitemap(&parameters));
|
|
<span class="boring">}
|
|
</span></code></pre></pre>
|
|
<div style="break-before: page; page-break-before: always;"></div><h1 id="routing-update-callback"><a class="header" href="#routing-update-callback">Routing Update Callback</a></h1>
|
|
<p>In some cases we might want to run custom code when the current route changes.
|
|
For this reason, the <a href="https://docs.rs/dioxus-router/latest/dioxus_router/hooks/struct.RouterConfiguration.html"><code>RouterConfiguration</code></a> exposes an <code>on_update</code> field.</p>
|
|
<h2 id="how-does-the-callback-behave"><a class="header" href="#how-does-the-callback-behave">How does the callback behave?</a></h2>
|
|
<p>The <code>on_update</code> is called whenever the current routing information changes. It
|
|
is called after the router updated its internal state, but before depended
|
|
components and hooks are updated.</p>
|
|
<p>If the callback returns a <a href="https://docs.rs/dioxus-router-core/latest/dioxus_router_core/navigation/enum.NavigationTarget.html"><code>NavigationTarget</code></a>, the router will replace the
|
|
current location with the specified target. It will not call the
|
|
<code>on_update</code> again.</p>
|
|
<p>If at any point the router encounters a
|
|
<a href="features/./failures/index.html">navigation failure</a>, it will go to the appropriate state
|
|
without calling the <code>on_update</code>. It doesn't matter if the invalid target
|
|
initiated the navigation, was found as a redirect target or returned by the
|
|
<code>on_update</code> itself.</p>
|
|
<h2 id="code-example-3"><a class="header" href="#code-example-3">Code Example</a></h2>
|
|
<pre><pre class="playground"><code class="language-rust edition2021">
|
|
<span class="boring">#![allow(unused)]
|
|
</span><span class="boring">fn main() {
|
|
</span><span class="boring">// Hidden lines (like this one) make the documentation tests work.
|
|
</span><span class="boring">extern crate dioxus;
|
|
</span><span class="boring">extern crate dioxus_router;
|
|
</span><span class="boring">extern crate dioxus_ssr;
|
|
</span><span class="boring">
|
|
</span>use std::sync::Arc;
|
|
|
|
use dioxus::prelude::*;
|
|
use dioxus_router::prelude::*;
|
|
|
|
fn App(cx: Scope) -> Element {
|
|
use_router(
|
|
cx,
|
|
&|| RouterConfiguration {
|
|
<span class="boring"> synchronous: true,
|
|
</span> on_update: Some(Arc::new(|state| -> Option<NavigationTarget> {
|
|
if state.path == "/" {
|
|
return Some("/home".into());
|
|
}
|
|
|
|
None
|
|
})),
|
|
..Default::default()
|
|
},
|
|
&|| Segment::empty().fixed("home", comp(Content))
|
|
);
|
|
|
|
render! {
|
|
Outlet { }
|
|
}
|
|
}
|
|
|
|
fn Content(cx: Scope) -> Element {
|
|
render! {
|
|
p { "Some content" }
|
|
}
|
|
}
|
|
<span class="boring">
|
|
</span><span class="boring">let mut vdom = VirtualDom::new(App);
|
|
</span><span class="boring">vdom.rebuild();
|
|
</span><span class="boring">assert_eq!(dioxus_ssr::render(&mut vdom), "<p>Some content</p>");
|
|
</span><span class="boring">}
|
|
</span></code></pre></pre>
|
|
<div style="break-before: page; page-break-before: always;"></div><h1 id="overview"><a class="header" href="#overview">Overview</a></h1>
|
|
<p>In this guide you'll learn to effectively use Dioxus Router whether you're
|
|
building a small todo app or the next FAANG company. We will create a small
|
|
website with a blog, homepage, and more!</p>
|
|
<blockquote>
|
|
<p>To follow along with the router example, you'll need a working Dioxus app.
|
|
Check out the <a href="https://dioxuslabs.com/guide/">Dioxus book</a> to get started.</p>
|
|
</blockquote>
|
|
<blockquote>
|
|
<p>Make sure to add Dioxus Router as a dependency, as explained in the
|
|
<a href="example/../index.html">introduction</a>.</p>
|
|
</blockquote>
|
|
<h2 id="youll-learn-how-to"><a class="header" href="#youll-learn-how-to">You'll learn how to</a></h2>
|
|
<ul>
|
|
<li>Create routes and render "pages".</li>
|
|
<li>Utilize nested routes, create a navigation bar, and render content for a
|
|
set of routes.</li>
|
|
<li>Gather URL parameters to dynamically display content.</li>
|
|
<li>Redirect your visitors wherever you want.</li>
|
|
</ul>
|
|
<blockquote>
|
|
<p><strong>Disclaimer</strong></p>
|
|
<p>The example will only display the features of Dioxus Router. It will not
|
|
include any actual functionality. To keep things simple we will only be using
|
|
a single file, this is not the recommended way of doing things with a real
|
|
application.</p>
|
|
</blockquote>
|
|
<p>You can find the complete application in the <a href="example/./full-code.html">full code</a>
|
|
chapter.</p>
|
|
<div style="break-before: page; page-break-before: always;"></div><h1 id="creating-our-first-route"><a class="header" href="#creating-our-first-route">Creating Our First Route</a></h1>
|
|
<p>In this chapter, we will start utilizing Dioxus Router and add a homepage and a
|
|
404 page to our project.</p>
|
|
<h2 id="fundamentals"><a class="header" href="#fundamentals">Fundamentals</a></h2>
|
|
<p>Dioxus Router works based on a <a href="https://docs.rs/dioxus-router/latest/dioxus_router/hooks/fn.use_router.html"><code>use_router</code></a> hook, a route definition in pure
|
|
rust and <a href="https://docs.rs/dioxus-router/latest/dioxus_router/components/fn.Outlet.html"><code>Outlet</code></a> components. If you've ever used <a href="https://router.vuejs.org/">Vue Router</a>, you should
|
|
feel right at home with Dioxus Router.</p>
|
|
<p>First we need an actual page to route to! Let's add a homepage component:</p>
|
|
<pre><pre class="playground"><code class="language-rust no_run edition2021">
|
|
<span class="boring">#![allow(unused)]
|
|
</span><span class="boring">fn main() {
|
|
</span><span class="boring">// Hidden lines (like this one) make the documentation tests work.
|
|
</span><span class="boring">extern crate dioxus;
|
|
</span><span class="boring">use dioxus::prelude::*;
|
|
</span><span class="boring">
|
|
</span>fn Home(cx: Scope) -> Element {
|
|
render! {
|
|
h1 { "Welcome to the Dioxus Blog!" }
|
|
}
|
|
}
|
|
<span class="boring">}
|
|
</span></code></pre></pre>
|
|
<h2 id="to-route-or-not-to-route"><a class="header" href="#to-route-or-not-to-route">To Route or Not to Route</a></h2>
|
|
<p>We want to use Dioxus Router to separate our application into different "pages".
|
|
Dioxus Router will then determine which page to render based on the URL path.</p>
|
|
<p>To start using Dioxus Router, we need to use the <a href="https://docs.rs/dioxus-router/latest/dioxus_router/hooks/fn.use_router.html"><code>use_router</code></a> hook. All other
|
|
hooks and components the router provides can only be used as a descendant of a
|
|
component calling <a href="https://docs.rs/dioxus-router/latest/dioxus_router/hooks/fn.use_router.html"><code>use_router</code></a>.</p>
|
|
<p>The <a href="https://docs.rs/dioxus-router/latest/dioxus_router/hooks/fn.use_router.html"><code>use_router</code></a> hook takes three arguments:</p>
|
|
<ol>
|
|
<li><code>cx</code>, which is a common argument for all hooks.</li>
|
|
<li>A <a href="https://docs.rs/dioxus-router/latest/dioxus_router/hooks/struct.RouterConfiguration.html"><code>RouterConfiguration</code></a>, which allows us to modify its behavior.</li>
|
|
<li>A definition of all routes the application contains, in the form of its root
|
|
<a href="https://docs.rs/dioxus-router-core/latest/dioxus_router_core/routes/struct.Segment.html"><code>Segment</code></a>.</li>
|
|
</ol>
|
|
<pre><pre class="playground"><code class="language-rust no_run edition2021">
|
|
<span class="boring">#![allow(unused)]
|
|
</span><span class="boring">fn main() {
|
|
</span><span class="boring">// Hidden lines (like this one) make the documentation tests work.
|
|
</span><span class="boring">extern crate dioxus;
|
|
</span>use dioxus::prelude::*;
|
|
<span class="boring">extern crate dioxus_router;
|
|
</span>use dioxus_router::prelude::*;
|
|
<span class="boring">fn Home(cx: Scope) -> Element { unimplemented!() }
|
|
</span>
|
|
fn App(cx: Scope) -> Element {
|
|
use_router(
|
|
cx,
|
|
&|| RouterConfiguration::default(),
|
|
&|| Segment::content(comp(Home))
|
|
);
|
|
|
|
render! {
|
|
Outlet { }
|
|
}
|
|
}
|
|
<span class="boring">}
|
|
</span></code></pre></pre>
|
|
<p>If you head to your application's browser tab, you should now see the text
|
|
<code>Welcome to Dioxus Blog!</code> when on the root URL (<code>http://localhost:8080/</code>). If
|
|
you enter a different path for the URL, nothing should be displayed.</p>
|
|
<p>This is because we told Dioxus Router to render the <code>Home</code> component only when
|
|
the URL path is <code>/</code>. The <em>index</em> (<code>Segment::content()</code>) functionality we used
|
|
basically emulates how web servers treat <code>index.html</code> files.</p>
|
|
<h2 id="what-if-a-route-doesnt-exist"><a class="header" href="#what-if-a-route-doesnt-exist">What if a Route Doesn't Exist?</a></h2>
|
|
<p>In our example Dioxus Router doesn't render anything. Many sites also have a
|
|
"404" page for when a URL path leads to nowhere. Dioxus Router can do this too!</p>
|
|
<p>First, we create a new <code>PageNotFound</code> component.</p>
|
|
<pre><pre class="playground"><code class="language-rust no_run edition2021">
|
|
<span class="boring">#![allow(unused)]
|
|
</span><span class="boring">fn main() {
|
|
</span><span class="boring">// Hidden lines (like this one) make the documentation tests work.
|
|
</span><span class="boring">extern crate dioxus;
|
|
</span><span class="boring">use dioxus::prelude::*;
|
|
</span><span class="boring">
|
|
</span>fn PageNotFound(cx: Scope) -> Element {
|
|
render! {
|
|
h1 { "Page not found" }
|
|
p { "We are terribly sorry, but the page you requested doesn't exist." }
|
|
}
|
|
}
|
|
<span class="boring">}
|
|
</span></code></pre></pre>
|
|
<p>Now to tell Dioxus Router to render our new component when no route exists.</p>
|
|
<pre><pre class="playground"><code class="language-rust no_run edition2021">
|
|
<span class="boring">#![allow(unused)]
|
|
</span><span class="boring">fn main() {
|
|
</span><span class="boring">// Hidden lines (like this one) make the documentation tests work.
|
|
</span><span class="boring">extern crate dioxus;
|
|
</span><span class="boring">use dioxus::prelude::*;
|
|
</span><span class="boring">extern crate dioxus_router;
|
|
</span><span class="boring">use dioxus_router::prelude::*;
|
|
</span><span class="boring">fn Home(cx: Scope) -> Element { unimplemented!() }
|
|
</span><span class="boring">fn PageNotFound(cx: Scope) -> Element { unimplemented!() }
|
|
</span><span class="boring">
|
|
</span>fn App(cx: Scope) -> Element {
|
|
use_router(
|
|
cx,
|
|
&|| RouterConfiguration::default(),
|
|
&|| {
|
|
Segment::content(comp(Home))
|
|
.fallback(comp(PageNotFound)) // this is new
|
|
}
|
|
);
|
|
|
|
render! {
|
|
Outlet { }
|
|
}
|
|
}
|
|
<span class="boring">}
|
|
</span></code></pre></pre>
|
|
<p>Now when you go to a route that doesn't exist, you should see the page not found
|
|
text.</p>
|
|
<h2 id="conclusion"><a class="header" href="#conclusion">Conclusion</a></h2>
|
|
<p>In this chapter we learned how to create a route and tell Dioxus Router what
|
|
component to render when the URL path is <code>/</code>. We also created a 404 page to
|
|
handle when a route doesn't exist. Next, we'll create the blog portion of our
|
|
site. We will utilize nested routes and URL parameters.</p>
|
|
<div style="break-before: page; page-break-before: always;"></div><h1 id="building-a-nest"><a class="header" href="#building-a-nest">Building a Nest</a></h1>
|
|
<p>Not a bird's nest! A nest of routes!</p>
|
|
<p>In this chapter we will begin to build the blog portion of our site which will
|
|
include links, nested URLs, and URL parameters. We will also explore the use
|
|
case of rendering components directly in the component calling <a href="https://docs.rs/dioxus-router/latest/dioxus_router/hooks/fn.use_router.html"><code>use_router</code></a>.</p>
|
|
<h2 id="site-navigation"><a class="header" href="#site-navigation">Site Navigation</a></h2>
|
|
<p>Our site visitors won't know all the available pages and blogs on our site so we
|
|
should provide a navigation bar for them.
|
|
Let's create a new <code>NavBar</code> component:</p>
|
|
<pre><pre class="playground"><code class="language-rust no_run edition2021">
|
|
<span class="boring">#![allow(unused)]
|
|
</span><span class="boring">fn main() {
|
|
</span><span class="boring">// Hidden lines (like this one) make the documentation tests work.
|
|
</span><span class="boring">extern crate dioxus;
|
|
</span><span class="boring">use dioxus::prelude::*;
|
|
</span><span class="boring">extern crate dioxus_router;
|
|
</span><span class="boring">use dioxus_router::prelude::*;
|
|
</span><span class="boring">
|
|
</span>fn NavBar(cx: Scope) -> Element {
|
|
render! {
|
|
nav {
|
|
ul { }
|
|
}
|
|
}
|
|
}
|
|
<span class="boring">}
|
|
</span></code></pre></pre>
|
|
<p>Our navbar will be a list of links going between our pages. We could always use
|
|
an HTML anchor element but that would cause our page to reload unnecessarily.
|
|
Instead we want to use the <a href="https://docs.rs/dioxus-router/latest/dioxus_router/components/fn.Link.html"><code>Link</code></a> component provided by Dioxus Router.</p>
|
|
<p>The <a href="https://docs.rs/dioxus-router/latest/dioxus_router/components/fn.Link.html"><code>Link</code></a> is similar to a regular <code>a</code> tag. It takes a target (for now a path,
|
|
more on other targets later) and an element. Let's add our links</p>
|
|
<pre><pre class="playground"><code class="language-rust no_run edition2021">
|
|
<span class="boring">#![allow(unused)]
|
|
</span><span class="boring">fn main() {
|
|
</span><span class="boring">// Hidden lines (like this one) make the documentation tests work.
|
|
</span><span class="boring">extern crate dioxus;
|
|
</span><span class="boring">use dioxus::prelude::*;
|
|
</span><span class="boring">extern crate dioxus_router;
|
|
</span><span class="boring">use dioxus_router::prelude::*;
|
|
</span><span class="boring">
|
|
</span>fn NavBar(cx: Scope) -> Element {
|
|
render! {
|
|
nav {
|
|
ul {
|
|
// new stuff starts here
|
|
li { Link {
|
|
target: NavigationTarget::Internal(String::from("/")),
|
|
"Home"
|
|
} }
|
|
li { Link {
|
|
target: "/blog", // short form
|
|
"Blog"
|
|
} }
|
|
// new stuff ends here
|
|
}
|
|
}
|
|
}
|
|
}
|
|
<span class="boring">}
|
|
</span></code></pre></pre>
|
|
<blockquote>
|
|
<p>Using this method, the <a href="https://docs.rs/dioxus-router/latest/dioxus_router/components/fn.Link.html"><code>Link</code></a> component only works for links within our
|
|
application. To learn more about navigation targets see
|
|
<a href="example/./navigation-targets.html">here</a>.</p>
|
|
</blockquote>
|
|
<p>And finally, we add the navbar component in our app component:</p>
|
|
<pre><pre class="playground"><code class="language-rust no_run edition2021">
|
|
<span class="boring">#![allow(unused)]
|
|
</span><span class="boring">fn main() {
|
|
</span><span class="boring">// Hidden lines (like this one) make the documentation tests work.
|
|
</span><span class="boring">extern crate dioxus;
|
|
</span><span class="boring">use dioxus::prelude::*;
|
|
</span><span class="boring">extern crate dioxus_router;
|
|
</span><span class="boring">use dioxus_router::prelude::*;
|
|
</span><span class="boring">fn Home(cx: Scope) -> Element { unimplemented!() }
|
|
</span><span class="boring">fn NavBar(cx: Scope) -> Element { unimplemented!() }
|
|
</span><span class="boring">fn PageNotFound(cx: Scope) -> Element { unimplemented!() }
|
|
</span><span class="boring">
|
|
</span>fn App(cx: Scope) -> Element {
|
|
use_router(
|
|
cx,
|
|
&|| RouterConfiguration::default(),
|
|
&|| Segment::content(comp(Home)).fallback(comp(PageNotFound))
|
|
);
|
|
|
|
render! {
|
|
NavBar { } // this is new
|
|
Outlet { }
|
|
}
|
|
}
|
|
<span class="boring">}
|
|
</span></code></pre></pre>
|
|
<p>Now you should see a list of links near the top of your page. Click on one and
|
|
you should seamlessly travel between pages.</p>
|
|
<h3 id="active-link-styling"><a class="header" href="#active-link-styling">Active Link Styling</a></h3>
|
|
<p>You might want to style links differently, when their page is currently open.
|
|
To achieve this, we can tell the <a href="https://docs.rs/dioxus-router/latest/dioxus_router/components/fn.Link.html"><code>Link</code></a> to give its internal <code>a</code> tag a class
|
|
in that case.</p>
|
|
<pre><pre class="playground"><code class="language-rust no_run edition2021">
|
|
<span class="boring">#![allow(unused)]
|
|
</span><span class="boring">fn main() {
|
|
</span><span class="boring">// Hidden lines (like this one) make the documentation tests work.
|
|
</span><span class="boring">extern crate dioxus;
|
|
</span><span class="boring">use dioxus::prelude::*;
|
|
</span><span class="boring">extern crate dioxus_router;
|
|
</span><span class="boring">use dioxus_router::prelude::*;
|
|
</span><span class="boring">
|
|
</span>fn NavBar(cx: Scope) -> Element {
|
|
render! {
|
|
nav {
|
|
ul {
|
|
li { Link {
|
|
target: NavigationTarget::Internal(String::from("/")),
|
|
active_class: "active", // this is new
|
|
"Home"
|
|
} }
|
|
li { Link {
|
|
target: "/blog",
|
|
active_class: "active", // this is new
|
|
"Blog"
|
|
} }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
<span class="boring">}
|
|
</span></code></pre></pre>
|
|
<blockquote>
|
|
<p>This will not be reflected in the <a href="example/./full-code.html">full example code</a>.</p>
|
|
</blockquote>
|
|
<h2 id="url-parameters-and-nested-routes"><a class="header" href="#url-parameters-and-nested-routes">URL Parameters and Nested Routes</a></h2>
|
|
<p>Many websites such as GitHub put parameters in their URL. For example,
|
|
<code>https://github.com/DioxusLabs</code> utilizes the text after the domain to
|
|
dynamically search and display content about an organization.</p>
|
|
<p>We want to store our blogs in a database and load them as needed. This'll help
|
|
prevent our app from being bloated therefor providing faster load times. We also
|
|
want our users to be able to send people a link to a specific blog post.</p>
|
|
<p>We could utilize a search page that loads a blog when clicked but then our users
|
|
won't be able to share our blogs easily. This is where URL parameters come in.</p>
|
|
<p>The path to our blog will look like <code>/blog/myBlogPage</code>, <code>myBlogPage</code> being the
|
|
URL parameter.</p>
|
|
<p>First, lets create a component that wraps around all blog content. This allows
|
|
us to add a heading that tells the user they are on the blog.</p>
|
|
<pre><pre class="playground"><code class="language-rust no_run edition2021">
|
|
<span class="boring">#![allow(unused)]
|
|
</span><span class="boring">fn main() {
|
|
</span><span class="boring">// Hidden lines (like this one) make the documentation tests work.
|
|
</span><span class="boring">extern crate dioxus;
|
|
</span><span class="boring">use dioxus::prelude::*;
|
|
</span><span class="boring">extern crate dioxus_router;
|
|
</span><span class="boring">use dioxus_router::prelude::*;
|
|
</span><span class="boring">
|
|
</span>fn Blog(cx: Scope) -> Element {
|
|
render! {
|
|
h1 { "Blog" }
|
|
Outlet {}
|
|
}
|
|
}
|
|
<span class="boring">}
|
|
</span></code></pre></pre>
|
|
<blockquote>
|
|
<p>Note the <code>Outlet { }</code> component. For the components of a nested route to be
|
|
rendered, we need an equally nested outlet. For more details, see the
|
|
<a href="example/../features/routes/nested.html">nested routes</a> chapter of the features section.</p>
|
|
</blockquote>
|
|
<p>Now we'll create another index component, that'll be displayed when no blog post
|
|
is selected:</p>
|
|
<pre><pre class="playground"><code class="language-rust no_run edition2021">
|
|
<span class="boring">#![allow(unused)]
|
|
</span><span class="boring">fn main() {
|
|
</span><span class="boring">// Hidden lines (like this one) make the documentation tests work.
|
|
</span><span class="boring">extern crate dioxus;
|
|
</span><span class="boring">use dioxus::prelude::*;
|
|
</span><span class="boring">extern crate dioxus_router;
|
|
</span><span class="boring">use dioxus_router::prelude::*;
|
|
</span><span class="boring">
|
|
</span>fn BlogList(cx: Scope) -> Element {
|
|
render! {
|
|
h2 { "Choose a post" }
|
|
ul {
|
|
li { Link {
|
|
target: "/blog/1",
|
|
"Read the first blog post"
|
|
} }
|
|
li { Link {
|
|
target: "/blog/2",
|
|
"Read the second blog post"
|
|
} }
|
|
}
|
|
}
|
|
}
|
|
<span class="boring">}
|
|
</span></code></pre></pre>
|
|
<p>We also need to create a component that displays an actual blog post. Within
|
|
this component we can use the <code>use_route</code> hook to gain access to our URL
|
|
parameters:</p>
|
|
<pre><pre class="playground"><code class="language-rust no_run edition2021">
|
|
<span class="boring">#![allow(unused)]
|
|
</span><span class="boring">fn main() {
|
|
</span><span class="boring">// Hidden lines (like this one) make the documentation tests work.
|
|
</span><span class="boring">extern crate dioxus;
|
|
</span><span class="boring">use dioxus::prelude::*;
|
|
</span><span class="boring">extern crate dioxus_router;
|
|
</span><span class="boring">use dioxus_router::prelude::*;
|
|
</span><span class="boring">
|
|
</span>struct PostId;
|
|
|
|
fn BlogPost(cx: Scope) -> Element {
|
|
let route = use_route(cx).unwrap();
|
|
|
|
let post_id = route.parameter::<PostId>();
|
|
let post = post_id
|
|
.map(|id| id.to_string())
|
|
.unwrap_or(String::from("unknown"));
|
|
|
|
render! {
|
|
h2 { "Blog Post: {post}"}
|
|
}
|
|
}
|
|
<span class="boring">}
|
|
</span></code></pre></pre>
|
|
<p>Finally, let's tell our router about those components.</p>
|
|
<pre><pre class="playground"><code class="language-rust no_run edition2021">
|
|
<span class="boring">#![allow(unused)]
|
|
</span><span class="boring">fn main() {
|
|
</span><span class="boring">// Hidden lines (like this one) make the documentation tests work.
|
|
</span><span class="boring">extern crate dioxus;
|
|
</span><span class="boring">use dioxus::prelude::*;
|
|
</span><span class="boring">extern crate dioxus_router;
|
|
</span><span class="boring">use dioxus_router::prelude::*;
|
|
</span><span class="boring">fn Blog(cx: Scope) -> Element { unimplemented!() }
|
|
</span><span class="boring">fn BlogList(cx: Scope) -> Element { unimplemented!() }
|
|
</span><span class="boring">struct PostId;
|
|
</span><span class="boring">fn BlogPost(cx: Scope) -> Element { unimplemented!() }
|
|
</span><span class="boring">fn Home(cx: Scope) -> Element { unimplemented!() }
|
|
</span><span class="boring">fn NavBar(cx: Scope) -> Element { unimplemented!() }
|
|
</span><span class="boring">fn PageNotFound(cx: Scope) -> Element { unimplemented!() }
|
|
</span><span class="boring">
|
|
</span>fn App(cx: Scope) -> Element {
|
|
use_router(
|
|
cx,
|
|
&|| RouterConfiguration::default(),
|
|
&|| {
|
|
Segment::content(comp(Home))
|
|
// new stuff starts here
|
|
.fixed("blog", Route::content(comp(Blog)).nested(
|
|
Segment::content(comp(BlogList))
|
|
.catch_all((comp(BlogPost), PostId { }))
|
|
))
|
|
// new stuff ends here
|
|
.fallback(comp(PageNotFound))
|
|
}
|
|
);
|
|
|
|
render! {
|
|
NavBar { }
|
|
Outlet { }
|
|
}
|
|
}
|
|
<span class="boring">}
|
|
</span></code></pre></pre>
|
|
<p>That's it! If you head to <code>/blog/1</code> you should see our sample post.</p>
|
|
<h2 id="conclusion-1"><a class="header" href="#conclusion-1">Conclusion</a></h2>
|
|
<p>In this chapter we utilized Dioxus Router's Link, URL Parameter, and <code>use_route</code>
|
|
functionality to build the blog portion of our application. In the next chapter,
|
|
we will go over how navigation targets (like the one we passed to our links)
|
|
work.</p>
|
|
<div style="break-before: page; page-break-before: always;"></div><h1 id="navigation-targets"><a class="header" href="#navigation-targets">Navigation Targets</a></h1>
|
|
<p>In the previous chapter we learned how to create links to pages within our app.
|
|
We told them where to go using the <code>target</code> property. This property takes a
|
|
<a href="https://docs.rs/dioxus-router-core/latest/dioxus_router_core/navigation/enum.NavigationTarget.html"><code>NavigationTarget</code></a>.</p>
|
|
<h2 id="what-is-a-navigation-target"><a class="header" href="#what-is-a-navigation-target">What is a navigation target?</a></h2>
|
|
<p>A <a href="https://docs.rs/dioxus-router-core/latest/dioxus_router_core/navigation/enum.NavigationTarget.html"><code>NavigationTarget</code></a> is similar to the <code>href</code> of an HTML anchor element.It
|
|
tells the router where to navigate to. The Dioxus Router knows three kinds of
|
|
navigation targets:</p>
|
|
<ul>
|
|
<li><a href="https://docs.rs/dioxus-router-core/latest/dioxus_router_core/navigation/enum.NavigationTarget.html#variant.Internal"><code>Internal</code></a>: we already saw that. It's basically an <code>href</code>, but cannot
|
|
link to content outside our app.</li>
|
|
<li><a href="https://docs.rs/dioxus-router-core/latest/dioxus_router_core/navigation/enum.NavigationTarget.html#variant.External"><code>External</code></a>: This works exactly like an HTML anchors <code>href</code>. In fact,
|
|
it is just passed through. Don't use this for in-app navigation as it'll
|
|
trigger a page reload by the browser.</li>
|
|
<li><a href="https://docs.rs/dioxus-router-core/latest/dioxus_router_core/navigation/enum.NavigationTarget.html#variant.Named"><code>Named</code></a>: this is the most interesting form of navigation target. We'll look
|
|
at it in detail in this chapter.</li>
|
|
</ul>
|
|
<h2 id="external-navigation-1"><a class="header" href="#external-navigation-1">External navigation</a></h2>
|
|
<p>If we need a link to an external page we can do it like this:</p>
|
|
<pre><pre class="playground"><code class="language-rust no_run edition2021">
|
|
<span class="boring">#![allow(unused)]
|
|
</span><span class="boring">fn main() {
|
|
</span><span class="boring">// Hidden lines (like this one) make the documentation tests work.
|
|
</span><span class="boring">extern crate dioxus;
|
|
</span><span class="boring">use dioxus::prelude::*;
|
|
</span><span class="boring">extern crate dioxus_router;
|
|
</span><span class="boring">use dioxus_router::prelude::*;
|
|
</span><span class="boring">
|
|
</span>fn GoToDioxus(cx: Scope) -> Element {
|
|
render! {
|
|
Link {
|
|
target: NavigationTarget::External("https://dioxuslabs.com".into()),
|
|
"Explicit ExternalTarget target"
|
|
}
|
|
Link {
|
|
target: "https://dioxuslabs.com", // short form
|
|
"Implicit ExternalTarget target"
|
|
}
|
|
}
|
|
}
|
|
<span class="boring">}
|
|
</span></code></pre></pre>
|
|
<blockquote>
|
|
<p>Note that we can use a <code>str</code>, just like with <a href="https://docs.rs/dioxus-router-core/latest/dioxus_router_core/navigation/enum.NavigationTarget.html#variant.Internal"><code>Internal</code></a>s. The router will
|
|
convert a <code>str</code> to an <a href="https://docs.rs/dioxus-router-core/latest/dioxus_router_core/navigation/enum.NavigationTarget.html#variant.External"><code>External</code></a> if the URL is absolute.</p>
|
|
</blockquote>
|
|
<h2 id="named-navigation-1"><a class="header" href="#named-navigation-1">Named navigation</a></h2>
|
|
<p>When defining our routes, we can optionally give them unique static names. This
|
|
is required for a feature we call named navigation.</p>
|
|
<p>Up to now, when creating links we told the router the exact path to go to. With
|
|
named navigation we instead give it a name, and let it figure out the path.</p>
|
|
<p>This has several advantages:</p>
|
|
<ul>
|
|
<li>We don't have to remember absolute paths or care about what the current path
|
|
is.</li>
|
|
<li>Changing paths later on won't break internal links.</li>
|
|
<li>Paths can easily be localized without affecting app logic.</li>
|
|
<li>The compiler makes sure we don't have typos.</li>
|
|
</ul>
|
|
<p>Let's try that now! First, we give our blog post route a name. We can reuse our
|
|
<code>BlogPost</code> component as a name.</p>
|
|
<pre><pre class="playground"><code class="language-rust no_run edition2021">
|
|
<span class="boring">#![allow(unused)]
|
|
</span><span class="boring">fn main() {
|
|
</span><span class="boring">// Hidden lines (like this one) make the documentation tests work.
|
|
</span><span class="boring">extern crate dioxus;
|
|
</span><span class="boring">use dioxus::prelude::*;
|
|
</span><span class="boring">extern crate dioxus_router;
|
|
</span><span class="boring">use dioxus_router::prelude::*;
|
|
</span><span class="boring">fn Blog(cx: Scope) -> Element { unimplemented!() }
|
|
</span><span class="boring">fn BlogList(cx: Scope) -> Element { unimplemented!() }
|
|
</span><span class="boring">struct PostId;
|
|
</span><span class="boring">fn BlogPost(cx: Scope) -> Element { unimplemented!() }
|
|
</span><span class="boring">fn Home(cx: Scope) -> Element { unimplemented!() }
|
|
</span><span class="boring">fn PageNotFound(cx: Scope) -> Element { unimplemented!() }
|
|
</span><span class="boring">
|
|
</span>struct BlogPostName;
|
|
|
|
fn App(cx: Scope) -> Element {
|
|
use_router(
|
|
cx,
|
|
&|| RouterConfiguration::default(),
|
|
&|| {
|
|
Segment::content(comp(Home))
|
|
.fixed("blog", Route::content(comp(Blog)).nested(
|
|
Segment::content(comp(BlogList)).catch_all(
|
|
ParameterRoute::content::<PostId>(comp(BlogPost))
|
|
.name::<BlogPostName>() // this is new
|
|
)
|
|
))
|
|
.fallback(comp(PageNotFound))
|
|
}
|
|
);
|
|
|
|
// ...
|
|
<span class="boring"> unimplemented!()
|
|
</span>}
|
|
<span class="boring">}
|
|
</span></code></pre></pre>
|
|
<p>Now we can change the targets of the links in our <code>BlogList</code> component.</p>
|
|
<pre><pre class="playground"><code class="language-rust no_run edition2021">
|
|
<span class="boring">#![allow(unused)]
|
|
</span><span class="boring">fn main() {
|
|
</span><span class="boring">// Hidden lines (like this one) make the documentation tests work.
|
|
</span><span class="boring">extern crate dioxus;
|
|
</span><span class="boring">use dioxus::prelude::*;
|
|
</span><span class="boring">extern crate dioxus_router;
|
|
</span><span class="boring">use dioxus_router::prelude::*;
|
|
</span><span class="boring">struct PostId;
|
|
</span><span class="boring">struct BlogPostName;
|
|
</span><span class="boring">fn BlogPost(cx: Scope) -> Element { unimplemented!() }
|
|
</span><span class="boring">
|
|
</span>fn BlogList(cx: Scope) -> Element {
|
|
render! {
|
|
h2 { "Choose a post" }
|
|
ul {
|
|
li { Link {
|
|
target: named::<BlogPostName>().parameter::<PostId>("1"),
|
|
"Read the first blog post"
|
|
} }
|
|
li { Link {
|
|
target: named::<BlogPostName>()
|
|
.parameter::<PostId>("1")
|
|
.query("query"),
|
|
"Read the second blog post"
|
|
} }
|
|
}
|
|
}
|
|
}
|
|
<span class="boring">}
|
|
</span></code></pre></pre>
|
|
<p>As you can see, a <a href="https://docs.rs/dioxus-router-core/latest/dioxus_router_core/navigation/enum.NavigationTarget.html#variant.Named"><code>Named</code></a> requires three fields:</p>
|
|
<ol>
|
|
<li>the name to navigate to</li>
|
|
<li>a <code>Vec</code> containing all parameters that need to be inserted into the path</li>
|
|
<li>optionally a query string to use.</li>
|
|
</ol>
|
|
<h3 id="the-special-root-index-name"><a class="header" href="#the-special-root-index-name">The special root index name</a></h3>
|
|
<p>Whether we define any names or not, the router always knows about the
|
|
<a href="https://docs.rs/dioxus-router-core/latest/dioxus_router_core/prelude/struct.RootIndex.html"><code>RootIndex</code></a> name. Navigating to it tells the router to go to <code>/</code>.</p>
|
|
<p>We can change the link in our <code>NavBar</code> component to take advantage of that.</p>
|
|
<pre><pre class="playground"><code class="language-rust no_run edition2021">
|
|
<span class="boring">#![allow(unused)]
|
|
</span><span class="boring">fn main() {
|
|
</span><span class="boring">// Hidden lines (like this one) make the documentation tests work.
|
|
</span><span class="boring">extern crate dioxus;
|
|
</span><span class="boring">use dioxus::prelude::*;
|
|
</span><span class="boring">extern crate dioxus_router;
|
|
</span><span class="boring">use dioxus_router::prelude::*;
|
|
</span><span class="boring">
|
|
</span>fn NavBar(cx: Scope) -> Element {
|
|
render! {
|
|
nav {
|
|
ul {
|
|
li { Link { target: named::<RootIndex>(), "Home" } }
|
|
li { Link { target: "/blog", "Blog" } }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
<span class="boring">}
|
|
</span></code></pre></pre>
|
|
<div style="break-before: page; page-break-before: always;"></div><h1 id="redirection-perfection"><a class="header" href="#redirection-perfection">Redirection Perfection</a></h1>
|
|
<p>You're well on your way to becoming a routing master!</p>
|
|
<p>In this chapter we will cover utilizing redirects so you can take Rickrolling to
|
|
the next level.</p>
|
|
<h2 id="what-is-this-redirect-thing"><a class="header" href="#what-is-this-redirect-thing">What Is This Redirect Thing?</a></h2>
|
|
<p>A redirect is very simple. When dioxus encounters a redirect while finding out
|
|
what components to render, it will redirect the user to the target of the
|
|
redirect.</p>
|
|
<p>As a simple example, let's say you want user to still land on your blog, even
|
|
if they used the path <code>/myblog</code>.</p>
|
|
<p>All we need to do is update our route definition in our app component:</p>
|
|
<pre><pre class="playground"><code class="language-rust no_run edition2021">
|
|
<span class="boring">#![allow(unused)]
|
|
</span><span class="boring">fn main() {
|
|
</span><span class="boring">// Hidden lines (like this one) make the documentation tests work.
|
|
</span><span class="boring">extern crate dioxus;
|
|
</span><span class="boring">use dioxus::prelude::*;
|
|
</span><span class="boring">extern crate dioxus_router;
|
|
</span><span class="boring">use dioxus_router::prelude::*;
|
|
</span><span class="boring">fn Blog(cx: Scope) -> Element { unimplemented!() }
|
|
</span><span class="boring">fn BlogList(cx: Scope) -> Element { unimplemented!() }
|
|
</span><span class="boring">struct PostId;
|
|
</span><span class="boring">struct BlogPostName;
|
|
</span><span class="boring">fn BlogPost(cx: Scope) -> Element { unimplemented!() }
|
|
</span><span class="boring">fn Home(cx: Scope) -> Element { unimplemented!() }
|
|
</span><span class="boring">fn NavBar(cx: Scope) -> Element { unimplemented!() }
|
|
</span><span class="boring">fn PageNotFound(cx: Scope) -> Element { unimplemented!() }
|
|
</span><span class="boring">fn App(cx: Scope) -> Element {
|
|
</span>use_router(
|
|
cx,
|
|
&|| RouterConfiguration::default(),
|
|
&|| {
|
|
Segment::content(comp(Home))
|
|
.fixed("blog", Route::content(comp(Blog)).nested(
|
|
Segment::content(comp(BlogList)).catch_all(
|
|
ParameterRoute::content::<PostId>(comp(BlogPost))
|
|
.name::<BlogPostName>()
|
|
)
|
|
))
|
|
.fixed("myblog", "/blog") // this is new
|
|
.fallback(comp(PageNotFound))
|
|
}
|
|
);
|
|
<span class="boring">unimplemented!()
|
|
</span><span class="boring">}
|
|
</span><span class="boring">}
|
|
</span></code></pre></pre>
|
|
<p>That's it! Now your users will be redirected to the blog.</p>
|
|
<p>Notice that the <code>"/blog"</code> <code>str</code> is a <a href="example/./navigation-targets.html">navigation target</a>.
|
|
We could also use external or named targets.</p>
|
|
<h3 id="conclusion-2"><a class="header" href="#conclusion-2">Conclusion</a></h3>
|
|
<p>Well done! You've completed the Dioxus Router guide book. You've built a small
|
|
application and learned about the many things you can do with Dioxus Router.
|
|
To continue your journey, you can find a list of challenges down below, or you
|
|
can check out the <a href="https://docs.rs/dioxus-router/">API reference</a>.</p>
|
|
<h3 id="challenges"><a class="header" href="#challenges">Challenges</a></h3>
|
|
<ul>
|
|
<li>Organize your components into seperate files for better maintainability.</li>
|
|
<li>Give your app some style if you haven't already.</li>
|
|
<li>Build an about page so your visitors know who you are.</li>
|
|
<li>Add a user system that uses URL parameters.</li>
|
|
<li>Create a simple admin system to create, delete, and edit blogs.</li>
|
|
<li>If you want to go to the max, hook up your application to a rest API and database.</li>
|
|
</ul>
|
|
<div style="break-before: page; page-break-before: always;"></div><h1 id="full-code-3"><a class="header" href="#full-code-3">Full Code</a></h1>
|
|
<pre><pre class="playground"><code class="language-rust no_run edition2021"><span class="boring">// Hidden lines (like this one) make the documentation tests work.
|
|
</span><span class="boring">extern crate dioxus;
|
|
</span><span class="boring">extern crate dioxus_router;
|
|
</span><span class="boring">extern crate dioxus_web;
|
|
</span>use dioxus::prelude::*;
|
|
use dioxus_router::prelude::*;
|
|
|
|
fn main() {
|
|
dioxus_web::launch(App);
|
|
}
|
|
|
|
fn App(cx: Scope) -> Element {
|
|
use_router(
|
|
cx,
|
|
&|| RouterConfiguration::default(),
|
|
&|| {
|
|
Segment::content(comp(Home))
|
|
.fixed("blog", Route::content(comp(Blog)).nested(
|
|
Segment::content(comp(BlogList)).catch_all(
|
|
ParameterRoute::content::<PostId>(comp(BlogPost))
|
|
.name::<BlogPostName>()
|
|
)
|
|
))
|
|
.fixed("myblog", "/blog") // this is new
|
|
.fallback(comp(PageNotFound))
|
|
}
|
|
);
|
|
|
|
render! {
|
|
NavBar {}
|
|
Outlet {}
|
|
}
|
|
}
|
|
|
|
fn NavBar(cx: Scope) -> Element {
|
|
render! {
|
|
nav {
|
|
ul {
|
|
li { Link { target: named::<RootIndex>(), "Home" } }
|
|
li { Link { target: "/blog", "Blog" } }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn Home(cx: Scope) -> Element {
|
|
render! {
|
|
h1 { "Welcome to the Dioxus Blog!" }
|
|
}
|
|
}
|
|
|
|
fn Blog(cx: Scope) -> Element {
|
|
render! {
|
|
h1 { "Blog" }
|
|
Outlet {}
|
|
}
|
|
}
|
|
|
|
fn BlogList(cx: Scope) -> Element {
|
|
render! {
|
|
h2 { "Choose a post" }
|
|
ul {
|
|
li { Link {
|
|
target: named::<BlogPostName>().parameter::<PostId>("1"),
|
|
"Read the first blog post"
|
|
} }
|
|
li { Link {
|
|
target: named::<BlogPostName>().parameter::<PostId>("2"),
|
|
"Read the second blog post"
|
|
} }
|
|
}
|
|
}
|
|
}
|
|
|
|
struct PostId;
|
|
struct BlogPostName;
|
|
fn BlogPost(cx: Scope) -> Element {
|
|
let route = use_route(cx).unwrap();
|
|
|
|
let post_id = route.parameter::<PostId>();
|
|
let post = post_id
|
|
.map(|id| id.to_string())
|
|
.unwrap_or(String::from("unknown"));
|
|
|
|
render! {
|
|
h2 { "Blog Post: {post}"}
|
|
}
|
|
}
|
|
|
|
fn PageNotFound(cx: Scope) -> Element {
|
|
render! {
|
|
h1 { "Page not found" }
|
|
p { "We are terribly sorry, but the page you requested doesn't exist." }
|
|
}
|
|
}
|
|
</code></pre></pre>
|
|
|
|
</main>
|
|
|
|
<nav class="nav-wrapper" aria-label="Page navigation">
|
|
<!-- Mobile navigation buttons -->
|
|
|
|
|
|
<div style="clear: both"></div>
|
|
</nav>
|
|
</div>
|
|
</div>
|
|
|
|
<nav class="nav-wide-wrapper" aria-label="Page navigation">
|
|
|
|
</nav>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<script type="text/javascript">
|
|
window.playground_copyable = true;
|
|
</script>
|
|
|
|
|
|
<script src="elasticlunr.min.js" type="text/javascript" charset="utf-8"></script>
|
|
<script src="mark.min.js" type="text/javascript" charset="utf-8"></script>
|
|
<script src="searcher.js" type="text/javascript" charset="utf-8"></script>
|
|
|
|
<script src="clipboard.min.js" type="text/javascript" charset="utf-8"></script>
|
|
<script src="highlight.js" type="text/javascript" charset="utf-8"></script>
|
|
<script src="book.js" type="text/javascript" charset="utf-8"></script>
|
|
|
|
<!-- Custom JS scripts -->
|
|
|
|
<script type="text/javascript">
|
|
window.addEventListener('load', function() {
|
|
window.setTimeout(window.print, 100);
|
|
});
|
|
</script>
|
|
|
|
</body>
|
|
</html>
|