From 4034aa9c112ec036d332d40ec420c6169c0fd75f Mon Sep 17 00:00:00 2001 From: Greg Johnston Date: Sat, 4 Feb 2023 10:02:17 -0500 Subject: [PATCH] feature: add isomorphic `` component (closes #412) (#466) --- examples/hackernews/src/routes/nav.rs | 4 +- examples/router/src/lib.rs | 5 +++ integrations/actix/src/lib.rs | 1 + integrations/axum/src/lib.rs | 1 + router/Cargo.toml | 1 + router/src/components/mod.rs | 2 + router/src/components/redirect.rs | 65 +++++++++++++++++++++++++++ 7 files changed, 77 insertions(+), 2 deletions(-) create mode 100644 router/src/components/redirect.rs diff --git a/examples/hackernews/src/routes/nav.rs b/examples/hackernews/src/routes/nav.rs index 6ff776278..a599e0b50 100644 --- a/examples/hackernews/src/routes/nav.rs +++ b/examples/hackernews/src/routes/nav.rs @@ -1,4 +1,4 @@ -use leptos::{component, Scope, IntoView, view}; +use leptos::{component, view, IntoView, Scope}; use leptos_router::*; #[component] @@ -6,7 +6,7 @@ pub fn Nav(cx: Scope) -> impl IntoView { view! { cx,
@@ -44,6 +45,10 @@ pub fn RouterExample(cx: Scope) -> impl IntoView { path="settings" view=move |cx| view! { cx, } /> + } + />
diff --git a/integrations/actix/src/lib.rs b/integrations/actix/src/lib.rs index d1811dc6f..1989cdc90 100644 --- a/integrations/actix/src/lib.rs +++ b/integrations/actix/src/lib.rs @@ -438,6 +438,7 @@ fn provide_contexts(cx: leptos::Scope, req: &HttpRequest, res_options: ResponseO provide_context(cx, MetaContext::new()); provide_context(cx, res_options); provide_context(cx, req.clone()); + provide_server_redirect(cx, move |path| redirect(cx, path)); } fn leptos_corrected_path(req: &HttpRequest) -> String { diff --git a/integrations/axum/src/lib.rs b/integrations/axum/src/lib.rs index c14b6f75a..df7f6c2d3 100644 --- a/integrations/axum/src/lib.rs +++ b/integrations/axum/src/lib.rs @@ -447,6 +447,7 @@ where provide_context(cx, MetaContext::new()); provide_context(cx, req_parts); provide_context(cx, default_res_options); + provide_server_redirect(cx, move |path| redirect(cx, path)); app_fn(cx).into_view(cx) } }; diff --git a/router/Cargo.toml b/router/Cargo.toml index e6c55a021..3236a3886 100644 --- a/router/Cargo.toml +++ b/router/Cargo.toml @@ -22,6 +22,7 @@ percent-encoding = "2" thiserror = "1" serde_urlencoded = "0.7" serde = "1" +tracing = "0.1" js-sys = { version = "0.3" } wasm-bindgen = { version = "0.2" } wasm-bindgen-futures = { version = "0.4" } diff --git a/router/src/components/mod.rs b/router/src/components/mod.rs index fa104bd08..c538d7f62 100644 --- a/router/src/components/mod.rs +++ b/router/src/components/mod.rs @@ -1,6 +1,7 @@ mod form; mod link; mod outlet; +mod redirect; mod route; mod router; mod routes; @@ -8,6 +9,7 @@ mod routes; pub use form::*; pub use link::*; pub use outlet::*; +pub use redirect::*; pub use route::*; pub use router::*; pub use routes::*; diff --git a/router/src/components/redirect.rs b/router/src/components/redirect.rs new file mode 100644 index 000000000..22a7d91f3 --- /dev/null +++ b/router/src/components/redirect.rs @@ -0,0 +1,65 @@ +use crate::{use_navigate, use_resolved_path, NavigateOptions}; +use leptos::{component, provide_context, use_context, IntoView, Scope}; +use std::rc::Rc; + +/// Redirects the user to a new URL, whether on the client side or on the server +/// side. If rendered on the server, this sets a `302` status code and sets a `Location` +/// header. If rendered in the browser, it uses client-side navigation to redirect. +/// In either case, it resolves the route relative to the current route. (To use +/// an absolute path, prefix it with `/`). +/// +/// **Note**: Support for server-side redirects is provided by the server framework +/// integrations (`leptos_actix` and `leptos_axum`). If you’re not using one of those +/// integrations, you should manually provide a way of redirecting on the server +/// using [provide_server_redirect]. +#[component] +pub fn Redirect

( + cx: Scope, + /// The relative path to which the user should be redirected. + path: P, + /// Navigation options to be used on the client side. + #[prop(optional)] + options: Option, +) -> impl IntoView +where + P: std::fmt::Display + 'static, +{ + // resolve relative path + let path = use_resolved_path(cx, move || path.to_string()); + let path = path.get().unwrap_or_else(|| "/".to_string()); + + // redirect on the server + if let Some(redirect_fn) = use_context::(cx) { + (redirect_fn.f)(&path); + } + + // redirect on the client + let navigate = use_navigate(cx); + navigate(&path, options.unwrap_or_default()) +} + +/// Wrapping type for a function provided as context to allow for +/// server-side redirects. See [provide_server_redirect] +/// and [Redirect]. +#[derive(Clone)] +pub struct ServerRedirectFunction { + f: Rc, +} + +impl std::fmt::Debug for ServerRedirectFunction { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ServerRedirectFunction").finish() + } +} + +/// Provides a function that can be used to redirect the user to another +/// absolute path, on the server. This should set a `302` status code and an +/// appropriate `Location` header. +pub fn provide_server_redirect(cx: Scope, handler: impl Fn(&str) + 'static) { + provide_context( + cx, + ServerRedirectFunction { + f: Rc::new(handler), + }, + ) +}