simplifying and updating server fns example

This commit is contained in:
Greg Johnston 2024-06-14 14:14:56 -04:00
parent 0a559935e7
commit b109c3e9a3
5 changed files with 87 additions and 67 deletions

View File

@ -7,14 +7,11 @@ edition = "2021"
crate-type = ["cdylib", "rlib"]
[dependencies]
console_log = "1.0"
console_error_panic_hook = "0.1"
futures = "0.3"
http = "1.0"
leptos = { path = "../../leptos" }
leptos_axum = { path = "../../integrations/axum", optional = true }
leptos_meta = { path = "../../meta" }
leptos_router = { path = "../../router" }
server_fn = { path = "../../server_fn", features = ["serde-lite", "rkyv", "multipart"] }
log = "0.4"
simple_logger = "4.0"
@ -34,17 +31,16 @@ pin-project-lite = "0.2.13"
dashmap = { version = "5.5.3", optional = true }
once_cell = { version = "1.19.0", optional = true }
async-broadcast = { version = "0.6.0", optional = true }
send_wrapper = "0.6.0"
[features]
hydrate = ["leptos/hydrate", "leptos_meta/hydrate", "leptos_router/hydrate"]
hydrate = ["leptos/hydrate"]
ssr = [
"dep:axum",
"dep:tower",
"dep:tower-http",
"dep:tokio",
"leptos/ssr",
"leptos_meta/ssr",
"leptos_router/ssr",
"dep:leptos_axum",
"dep:notify",
"dep:dashmap",

View File

@ -1,8 +1,6 @@
use futures::StreamExt;
use http::Method;
use leptos::{html::Input, *};
use leptos_meta::{provide_meta_context, Link, Meta, Stylesheet};
use leptos_router::{ActionForm, Route, Router, Routes};
use leptos::{html::Input, prelude::*, spawn::spawn_local};
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use server_fn::{
client::{browser::BrowserClient, Client},
@ -23,24 +21,35 @@ use strum::{Display, EnumString};
use wasm_bindgen::JsCast;
use web_sys::{FormData, HtmlFormElement, SubmitEvent};
#[component]
pub fn TodoApp() -> impl IntoView {
provide_meta_context();
pub fn shell(leptos_options: &LeptosOptions) -> impl IntoView {
view! {
<Meta name="color-scheme" content="dark light"/>
<Link rel="shortcut icon" type_="image/ico" href="/favicon.ico"/>
<Stylesheet id="leptos" href="/pkg/server_fns_axum.css"/>
<Router>
<header>
<h1>"Server Function Demo"</h1>
</header>
<main>
<Routes>
<Route path="" view=HomePage/>
</Routes>
</main>
</Router>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<AutoReload options=leptos_options.clone() />
<HydrationScripts options=leptos_options.clone()/>
<meta name="color-scheme" content="dark light"/>
<link rel="shortcut icon" type="image/ico" href="/favicon.ico"/>
<link rel="stylesheet" id="leptos" href="/pkg/server_fns_axum.css"/>
</head>
<body>
<App/>
</body>
</html>
}
}
#[component]
pub fn App() -> impl IntoView {
view! {
<header>
<h1>"Server Function Demo"</h1>
</header>
<main>
<HomePage/>
</main>
}
}
@ -87,8 +96,7 @@ pub fn SpawnLocal() -> impl IntoView {
}
let input_ref = NodeRef::<Input>::new();
let (shout_result, set_shout_result) =
create_signal("Click me".to_string());
let (shout_result, set_shout_result) = signal("Click me".to_string());
view! {
<h3>Using <code>spawn_local</code></h3>
@ -163,15 +171,15 @@ pub fn WithAnAction() -> impl IntoView {
// a server action can be created by using the server function's type name as a generic
// the type name defaults to the PascalCased function name
let action = create_server_action::<AddRow>();
let action = ServerAction::<AddRow>::new();
// this resource will hold the total number of rows
// passing it action.version() means it will refetch whenever the action resolves successfully
let row_count =
create_resource(move || action.version().get(), |_| get_rows());
Resource::new_serde(move || action.version().get(), |_| get_rows());
view! {
<h3>Using <code>create_action</code></h3>
<h3>Using <code>Action::new</code></h3>
<p>
"Some server functions are conceptually \"mutations,\", which change something on the server. "
"These often work well as actions."
@ -206,9 +214,9 @@ pub fn WithAnAction() -> impl IntoView {
/// message if the server function returns an error. Otherwise, it loads the new resource data.
#[component]
pub fn WithActionForm() -> impl IntoView {
let action = create_server_action::<AddRow>();
let action = ServerAction::<AddRow>::new();
let row_count =
create_resource(move || action.version().get(), |_| get_rows());
Resource::new_serde(move || action.version().get(), |_| get_rows());
view! {
<h3>Using <code>"<ActionForm/>"</code></h3>
@ -222,10 +230,10 @@ pub fn WithActionForm() -> impl IntoView {
name="text"
placeholder="Type something here."
/>
<button> Submit </button>
<button>Submit</button>
</ActionForm>
<p>You submitted: {move || format!("{:?}", action.input().get())}</p>
<p>The result was: {move || format!("{:?}", action.value().get())}</p>
//<p>You submitted: {move || format!("{:?}", action.input().get())}</p>
//<p>The result was: {move || format!("{:?}", action.value().get())}</p>
<Transition>archive underaligned: need alignment 4 but have alignment 1
<p>Total rows: {row_count}</p>
</Transition>
@ -261,7 +269,7 @@ pub async fn length_of_input(input: String) -> Result<usize, ServerFnError> {
#[component]
pub fn ServerFnArgumentExample() -> impl IntoView {
let input_ref = NodeRef::<Input>::new();
let (result, set_result) = create_signal(0);
let (result, set_result) = signal(0);
view! {
<h3>Custom arguments to the <code>#[server]</code> " macro"</h3>
@ -308,8 +316,8 @@ pub async fn rkyv_example(input: String) -> Result<String, ServerFnError> {
#[component]
pub fn RkyvExample() -> impl IntoView {
let input_ref = NodeRef::<Input>::new();
let (input, set_input) = create_signal(String::new());
let rkyv_result = create_resource(move || input.get(), rkyv_example);
let (input, set_input) = signal(String::new());
let rkyv_result = Resource::new_serde(move || input.get(), rkyv_example);
view! {
<h3>Using <code>rkyv</code> encoding</h3>
@ -362,8 +370,8 @@ pub fn FileUpload() -> impl IntoView {
Ok(count)
}
let upload_action = create_action(|data: &FormData| {
let data = data.clone();
let upload_action = Action::new_unsync(|data: &FormData| {
let data = data.to_owned();
// `MultipartData` implements `From<FormData>`
file_length(data.into())
});
@ -375,7 +383,7 @@ pub fn FileUpload() -> impl IntoView {
ev.prevent_default();
let target = ev.target().unwrap().unchecked_into::<HtmlFormElement>();
let form_data = FormData::new_with_form(&target).unwrap();
upload_action.dispatch(form_data);
upload_action.dispatch_unsync(form_data);
}>
<input type="file" name="file_to_upload"/>
<input type="submit"/>
@ -495,9 +503,9 @@ pub fn FileUploadWithProgress() -> impl IntoView {
Ok(TextStream::new(progress))
}
let (filename, set_filename) = create_signal(None);
let (max, set_max) = create_signal(None);
let (current, set_current) = create_signal(None);
let (filename, set_filename) = signal(None);
let (max, set_max) = signal(None);
let (current, set_current) = signal(None);
let on_submit = move |ev: SubmitEvent| {
ev.prevent_default();
let target = ev.target().unwrap().unchecked_into::<HtmlFormElement>();
@ -509,7 +517,7 @@ pub fn FileUploadWithProgress() -> impl IntoView {
let size = file.size() as usize;
set_filename.set(Some(filename.clone()));
set_max.set(Some(size));
set_current.set(None::<usize>);
set_current.set(None);
spawn_local(async move {
let mut progress = file_progress(filename)
@ -590,9 +598,9 @@ pub fn FileWatcher() -> impl IntoView {
Ok(TextStream::from(rx))
}
let (files, set_files) = create_signal(Vec::new());
let (files, set_files) = signal(Vec::new());
create_effect(move |_| {
Effect::new(move |_| {
spawn_local(async move {
while let Some(res) =
watched_files().await.unwrap().into_inner().next().await
@ -648,7 +656,7 @@ pub enum InvalidArgument {
#[component]
pub fn CustomErrorTypes() -> impl IntoView {
let input_ref = NodeRef::<Input>::new();
let (result, set_result) = create_signal(None);
let (result, set_result) = signal(None);
view! {
<h3>Using custom error types</h3>
@ -776,7 +784,7 @@ pub async fn why_not(
#[component]
pub fn CustomEncoding() -> impl IntoView {
let input_ref = NodeRef::<Input>::new();
let (result, set_result) = create_signal("foo".to_string());
let (result, set_result) = signal("foo".to_string());
view! {
<h3>Custom encodings</h3>
@ -788,7 +796,7 @@ pub fn CustomEncoding() -> impl IntoView {
on:click=move |_| {
let value = input_ref.get().unwrap().value();
spawn_local(async move {
let new_value = why_not(value, ", but in TOML!!!".to_string()).await.unwrap();
let new_value = why_not(value, ", but in TOML!!!".to_string()).await.unwrap();
set_result.set(new_value.0.modified);
});
}

View File

@ -1,14 +1,15 @@
use crate::{error_template::ErrorTemplate, errors::TodoAppError};
use axum::{
body::Body,
extract::State,
http::{Request, Response, StatusCode, Uri},
response::{IntoResponse, Response as AxumResponse},
};
use leptos::{view, Errors, LeptosOptions};
use leptos::{config::LeptosOptions, error::Errors};
use tower::ServiceExt;
use tower_http::services::ServeDir;
use crate::app::shell;
pub async fn file_and_error_handler(
uri: Uri,
State(options): State<LeptosOptions>,
@ -20,12 +21,8 @@ pub async fn file_and_error_handler(
if res.status() == StatusCode::OK {
res.into_response()
} else {
let mut errors = Errors::default();
errors.insert_with_default_key(TodoAppError::NotFound);
let handler = leptos_axum::render_app_to_stream(
options.to_owned(),
move || view! {<ErrorTemplate outside_errors=errors.clone()/>},
);
let handler =
leptos_axum::render_app_to_stream(move || shell(&options));
handler(req).await.into_response()
}
}

View File

@ -6,12 +6,10 @@ pub mod fallback;
#[cfg(feature = "ssr")]
pub mod middleware;
#[cfg(feature = "hydrate")]
#[wasm_bindgen::prelude::wasm_bindgen]
pub fn hydrate() {
use crate::app::TodoApp;
_ = console_log::init_with_level(log::Level::Error);
use crate::app::App;
console_error_panic_hook::set_once();
leptos::mount_to_body(TodoApp);
leptos::mount::hydrate_body(App);
}

View File

@ -1,6 +1,6 @@
use crate::{app::*, fallback::file_and_error_handler};
use axum::Router;
use leptos::{get_configuration, logging};
use leptos::{config::get_configuration, logging};
use leptos_axum::{generate_route_list, LeptosRoutes};
use server_fns_axum::*;
@ -13,11 +13,32 @@ async fn main() {
let conf = get_configuration(None).await.unwrap();
let leptos_options = conf.leptos_options;
let addr = leptos_options.site_addr;
let routes = generate_route_list(TodoApp);
let routes = generate_route_list(App);
// build our application with a route
let app = Router::new()
.leptos_routes(&leptos_options, routes, TodoApp)
.leptos_routes(&leptos_options, routes, {
let leptos_options = leptos_options.clone();
move || {
use leptos::prelude::*;
view! {
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
// <AutoReload options=app_state.leptos_options.clone() />
<HydrationScripts options=leptos_options.clone()/>
<link rel="stylesheet" id="leptos" href="/pkg/benwis_leptos.css"/>
<link rel="shortcut icon" type="image/ico" href="/favicon.ico"/>
</head>
<body>
<App/>
</body>
</html>
}
}})
.fallback(file_and_error_handler)
.with_state(leptos_options);