Skip to content

Commit 4034aa9

Browse files
authored
feature: add isomorphic <Redirect/> component (closes leptos-rs#412) (leptos-rs#466)
1 parent 45275ff commit 4034aa9

File tree

7 files changed

+77
-2
lines changed

7 files changed

+77
-2
lines changed

examples/hackernews/src/routes/nav.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
use leptos::{component, Scope, IntoView, view};
1+
use leptos::{component, view, IntoView, Scope};
22
use leptos_router::*;
33

44
#[component]
55
pub fn Nav(cx: Scope) -> impl IntoView {
66
view! { cx,
77
<header class="header">
88
<nav class="inner">
9-
<A href="/">
9+
<A href="/home">
1010
<strong>"HN"</strong>
1111
</A>
1212
<A href="/new">

examples/router/src/lib.rs

+5
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ pub fn RouterExample(cx: Scope) -> impl IntoView {
2020
<A exact=true href="/">"Contacts"</A>
2121
<A href="about">"About"</A>
2222
<A href="settings">"Settings"</A>
23+
<A href="redirect-home">"Redirect to Home"</A>
2324
</nav>
2425
<main>
2526
<Routes>
@@ -44,6 +45,10 @@ pub fn RouterExample(cx: Scope) -> impl IntoView {
4445
path="settings"
4546
view=move |cx| view! { cx, <Settings/> }
4647
/>
48+
<Route
49+
path="redirect-home"
50+
view=move |cx| view! { cx, <Redirect path="/"/> }
51+
/>
4752
</Routes>
4853
</main>
4954
</Router>

integrations/actix/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -438,6 +438,7 @@ fn provide_contexts(cx: leptos::Scope, req: &HttpRequest, res_options: ResponseO
438438
provide_context(cx, MetaContext::new());
439439
provide_context(cx, res_options);
440440
provide_context(cx, req.clone());
441+
provide_server_redirect(cx, move |path| redirect(cx, path));
441442
}
442443

443444
fn leptos_corrected_path(req: &HttpRequest) -> String {

integrations/axum/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -447,6 +447,7 @@ where
447447
provide_context(cx, MetaContext::new());
448448
provide_context(cx, req_parts);
449449
provide_context(cx, default_res_options);
450+
provide_server_redirect(cx, move |path| redirect(cx, path));
450451
app_fn(cx).into_view(cx)
451452
}
452453
};

router/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ percent-encoding = "2"
2222
thiserror = "1"
2323
serde_urlencoded = "0.7"
2424
serde = "1"
25+
tracing = "0.1"
2526
js-sys = { version = "0.3" }
2627
wasm-bindgen = { version = "0.2" }
2728
wasm-bindgen-futures = { version = "0.4" }

router/src/components/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
mod form;
22
mod link;
33
mod outlet;
4+
mod redirect;
45
mod route;
56
mod router;
67
mod routes;
78

89
pub use form::*;
910
pub use link::*;
1011
pub use outlet::*;
12+
pub use redirect::*;
1113
pub use route::*;
1214
pub use router::*;
1315
pub use routes::*;

router/src/components/redirect.rs

+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
use crate::{use_navigate, use_resolved_path, NavigateOptions};
2+
use leptos::{component, provide_context, use_context, IntoView, Scope};
3+
use std::rc::Rc;
4+
5+
/// Redirects the user to a new URL, whether on the client side or on the server
6+
/// side. If rendered on the server, this sets a `302` status code and sets a `Location`
7+
/// header. If rendered in the browser, it uses client-side navigation to redirect.
8+
/// In either case, it resolves the route relative to the current route. (To use
9+
/// an absolute path, prefix it with `/`).
10+
///
11+
/// **Note**: Support for server-side redirects is provided by the server framework
12+
/// integrations (`leptos_actix` and `leptos_axum`). If you’re not using one of those
13+
/// integrations, you should manually provide a way of redirecting on the server
14+
/// using [provide_server_redirect].
15+
#[component]
16+
pub fn Redirect<P>(
17+
cx: Scope,
18+
/// The relative path to which the user should be redirected.
19+
path: P,
20+
/// Navigation options to be used on the client side.
21+
#[prop(optional)]
22+
options: Option<NavigateOptions>,
23+
) -> impl IntoView
24+
where
25+
P: std::fmt::Display + 'static,
26+
{
27+
// resolve relative path
28+
let path = use_resolved_path(cx, move || path.to_string());
29+
let path = path.get().unwrap_or_else(|| "/".to_string());
30+
31+
// redirect on the server
32+
if let Some(redirect_fn) = use_context::<ServerRedirectFunction>(cx) {
33+
(redirect_fn.f)(&path);
34+
}
35+
36+
// redirect on the client
37+
let navigate = use_navigate(cx);
38+
navigate(&path, options.unwrap_or_default())
39+
}
40+
41+
/// Wrapping type for a function provided as context to allow for
42+
/// server-side redirects. See [provide_server_redirect]
43+
/// and [Redirect].
44+
#[derive(Clone)]
45+
pub struct ServerRedirectFunction {
46+
f: Rc<dyn Fn(&str)>,
47+
}
48+
49+
impl std::fmt::Debug for ServerRedirectFunction {
50+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
51+
f.debug_struct("ServerRedirectFunction").finish()
52+
}
53+
}
54+
55+
/// Provides a function that can be used to redirect the user to another
56+
/// absolute path, on the server. This should set a `302` status code and an
57+
/// appropriate `Location` header.
58+
pub fn provide_server_redirect(cx: Scope, handler: impl Fn(&str) + 'static) {
59+
provide_context(
60+
cx,
61+
ServerRedirectFunction {
62+
f: Rc::new(handler),
63+
},
64+
)
65+
}

0 commit comments

Comments
 (0)