From 9c2477a4cfc5d66a6df7b4d55823fd3df4229526 Mon Sep 17 00:00:00 2001
From: Greg Johnston
Date: Wed, 18 Sep 2024 20:25:03 -0400
Subject: [PATCH] Revert "chore: re-export `untrack` (#2991)"
This reverts commit f3b6d1f351a8192591dc39d59131f37bf50ad5fb.
---
examples/ssr_modes_axum/src/app.rs | 317 +++++++++++++++++++++++++----
leptos/src/lib.rs | 2 +-
router/src/components.rs | 1 -
3 files changed, 284 insertions(+), 36 deletions(-)
diff --git a/examples/ssr_modes_axum/src/app.rs b/examples/ssr_modes_axum/src/app.rs
index be343bb6f..33faa0d57 100644
--- a/examples/ssr_modes_axum/src/app.rs
+++ b/examples/ssr_modes_axum/src/app.rs
@@ -1,9 +1,17 @@
+use lazy_static::lazy_static;
use leptos::prelude::*;
-use leptos_meta::{provide_meta_context, MetaTags, Stylesheet, Title};
+use leptos_meta::MetaTags;
+use leptos_meta::*;
use leptos_router::{
- components::{ProtectedParentRoute, Route, Router, Routes, A},
- StaticSegment,
+ components::{FlatRoutes, ProtectedRoute, Route, Router},
+ hooks::use_params,
+ params::Params,
+ ParamSegment, SsrMode, StaticSegment,
};
+use serde::{Deserialize, Serialize};
+#[cfg(feature = "ssr")]
+use std::sync::atomic::{AtomicBool, Ordering};
+use thiserror::Error;
pub fn shell(options: LeptosOptions) -> impl IntoView {
view! {
@@ -23,34 +31,90 @@ pub fn shell(options: LeptosOptions) -> impl IntoView {
}
}
+#[cfg(feature = "ssr")]
+static IS_ADMIN: AtomicBool = AtomicBool::new(true);
+
+#[server]
+pub async fn is_admin() -> Result {
+ Ok(IS_ADMIN.load(Ordering::Relaxed))
+}
+
+#[server]
+pub async fn set_is_admin(is_admin: bool) -> Result<(), ServerFnError> {
+ IS_ADMIN.store(is_admin, Ordering::Relaxed);
+ Ok(())
+}
+
#[component]
pub fn App() -> impl IntoView {
+ // Provides context that manages stylesheets, titles, meta tags, etc.
provide_meta_context();
-
- let x = untrack(|| "foo");
+ let fallback = || view! { "Page not found." }.into_view();
+ let toggle_admin = ServerAction::::new();
+ let is_admin =
+ Resource::new(move || toggle_admin.version().get(), |_| is_admin());
view! {
+
+
+
- "Home"
- " | "
- "Dashboard"
- " | "
- "Profile"
+ "Home"
+ "Admin"
+
+
+
+
+
+ {move || {
+ if is_admin.get().and_then(Result::ok).unwrap_or_default() {
+ "Log Out"
+ } else {
+ "Log In"
+ }
+ }}
+
+
+
+
-
+
+ // We’ll load the home page with out-of-order streaming and
- "Protected Content" }
- condition=move || Some(false)
- redirect_path=|| "/".to_string()
- >
-
-
-
-
+
+ // We'll load the posts with async rendering, so they can set
+ // the title and metadata *after* loading the data
+
+
+
+
+
}
@@ -58,24 +122,209 @@ pub fn App() -> impl IntoView {
#[component]
fn HomePage() -> impl IntoView {
+ // load the posts
+ let posts = Resource::new(|| (), |_| list_post_metadata());
+ let posts = move || {
+ posts
+ .get()
+ .map(|n| n.unwrap_or_default())
+ .unwrap_or_default()
+ };
+
+ let posts2 = Resource::new(|| (), |_| list_post_metadata());
+ let posts2 = Resource::new(
+ || (),
+ move |_| async move { posts2.await.as_ref().map(Vec::len).unwrap_or(0) },
+ );
+
view! {
- "Welcome to the Home Page"
- "This page is accessible to everyone."
+ "My Great Blog"
+ "Loading posts..."
}>
+ "number of posts: " {Suspend::new(async move { posts2.await })}
+
+ "Loading posts..." }>
+
+
+ }
+}
+
+#[derive(Params, Copy, Clone, Debug, PartialEq, Eq)]
+pub struct PostParams {
+ id: Option,
+}
+
+#[component]
+fn Post() -> impl IntoView {
+ let query = use_params::();
+ let id = move || {
+ query.with(|q| {
+ q.as_ref()
+ .map(|q| q.id.unwrap_or_default())
+ .map_err(|_| PostError::InvalidId)
+ })
+ };
+ let post_resource = Resource::new_blocking(id, |id| async move {
+ match id {
+ Err(e) => Err(e),
+ Ok(id) => get_post(id)
+ .await
+ .map(|data| data.ok_or(PostError::PostNotFound))
+ .map_err(|_| PostError::ServerError),
+ }
+ });
+ let comments_resource = Resource::new(id, |id| async move {
+ match id {
+ Err(e) => Err(e),
+ Ok(id) => {
+ get_comments(id).await.map_err(|_| PostError::ServerError)
+ }
+ }
+ });
+
+ let post_view = Suspend::new(async move {
+ match post_resource.await {
+ Ok(Ok(post)) => {
+ Ok(view! {
+ {post.title.clone()}
+ {post.content.clone()}
+
+ // since we're using async rendering for this page,
+ // this metadata should be included in the actual HTML
+ // when it's first served
+
+
+ })
+ }
+ _ => Err(PostError::ServerError),
+ }
+ });
+ let comments_view = Suspend::new(async move {
+ match comments_resource.await {
+ Ok(comments) => Ok(view! {
+ "Comments"
+
+ {comments
+ .into_iter()
+ .map(|comment| view! { {comment} })
+ .collect_view()}
+
+
+ }),
+ _ => Err(PostError::ServerError),
+ }
+ });
+
+ view! {
+ "The world's best content."
+ "Loading post..." }>
+
+ "Something went wrong."
+
+ {move || {
+ errors
+ .get()
+ .into_iter()
+ .map(|(_, error)| view! { {error.to_string()} })
+ .collect::>()
+ }}
+
+
+
+ }
+ }>{post_view}
+
+ "Loading comments..." }>{comments_view}
}
}
#[component]
-fn DashboardPage() -> impl IntoView {
- view! {
- "Dashboard"
- "This is a protected page. You should only see this if you're authenticated."
- }
+pub fn Admin() -> impl IntoView {
+ view! { "You can only see this page if you're logged in."
}
}
-#[component]
-fn ProfilePage() -> impl IntoView {
- view! {
- "Profile"
- "This is another protected page. You should only see this if you're authenticated."
- }
+// Dummy API
+lazy_static! {
+ static ref POSTS: Vec = vec![
+ Post {
+ id: 0,
+ title: "My first post".to_string(),
+ content: "This is my first post".to_string(),
+ },
+ Post {
+ id: 1,
+ title: "My second post".to_string(),
+ content: "This is my second post".to_string(),
+ },
+ Post {
+ id: 2,
+ title: "My third post".to_string(),
+ content: "This is my third post".to_string(),
+ },
+ ];
+}
+
+#[derive(Error, Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
+pub enum PostError {
+ #[error("Invalid post ID.")]
+ InvalidId,
+ #[error("Post not found.")]
+ PostNotFound,
+ #[error("Server error.")]
+ ServerError,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
+pub struct Post {
+ id: usize,
+ title: String,
+ content: String,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
+pub struct PostMetadata {
+ id: usize,
+ title: String,
+}
+
+#[server]
+pub async fn list_post_metadata() -> Result, ServerFnError> {
+ tokio::time::sleep(std::time::Duration::from_secs(1)).await;
+ Ok(POSTS
+ .iter()
+ .map(|data| PostMetadata {
+ id: data.id,
+ title: data.title.clone(),
+ })
+ .collect())
+}
+
+#[server]
+pub async fn get_post(id: usize) -> Result, ServerFnError> {
+ tokio::time::sleep(std::time::Duration::from_secs(1)).await;
+ Ok(POSTS.iter().find(|post| post.id == id).cloned())
+}
+
+#[server]
+pub async fn get_comments(id: usize) -> Result, ServerFnError> {
+ tokio::time::sleep(std::time::Duration::from_secs(2)).await;
+ _ = id;
+ Ok(vec!["Some comment".into(), "Some other comment".into()])
}
diff --git a/leptos/src/lib.rs b/leptos/src/lib.rs
index 7bb7e6bed..9ff269bb7 100644
--- a/leptos/src/lib.rs
+++ b/leptos/src/lib.rs
@@ -168,7 +168,7 @@ pub mod prelude {
pub use leptos_server::*;
pub use oco_ref::*;
pub use reactive_graph::{
- actions::*, computed::*, effect::*, owner::*, signal::*, untrack,
+ actions::*, computed::*, effect::*, owner::*, signal::*,
wrappers::read::*,
};
pub use server_fn::{self, ServerFnError};
diff --git a/router/src/components.rs b/router/src/components.rs
index 538efd750..2b198aab2 100644
--- a/router/src/components.rs
+++ b/router/src/components.rs
@@ -443,7 +443,6 @@ pub fn Redirect(
) where
P: core::fmt::Display + 'static,
{
- leptos::logging::log!("running Redirect component");
// TODO resolve relative path
let path = path.to_string();