more fullstack cleanups: loaders serialize, ServeConfig only (#4785)
* some opinionated cleanups in dioxus-server * add serialize to loader * add `result` to Resource * clippy * fix * writable memos, resources, `onmount`, `.pending`, and `.value()` on actions * fix compile issues * fix typo
This commit is contained in:
1 parent
a3e40a5ea2
commit
7102bc3b6a
33 files changed
+2085
-672
No files matched your search
@@ -43,11 +43,11 @@ fn app() -> Element {
|
||||
h2 { class: "text-2xl font-bold", "Scan for Bluetooth Devices" }
|
||||
button {
|
||||
class: "inline-block w-full md:w-auto px-6 py-3 font-medium text-white bg-indigo-500 hover:bg-indigo-600 rounded transition duration-200",
|
||||
disabled: scan.is_pending(),
|
||||
disabled: scan.pending(),
|
||||
onclick: move |_| {
|
||||
scan.call();
|
||||
},
|
||||
if scan.is_pending() { "Scanning" } else { "Scan" }
|
||||
if scan.pending() { "Scanning" } else { "Scan" }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -64,8 +64,8 @@ fn app() -> Element {
|
||||
th { class: "pb-3 px-2 font-medium", "Security" }
|
||||
}
|
||||
}
|
||||
match scan.result() {
|
||||
None if scan.is_pending() => rsx! { "Scanning..." },
|
||||
match scan.value() {
|
||||
None if scan.pending() => rsx! { "Scanning..." },
|
||||
None => rsx! { "Press Scan to start scanning" },
|
||||
Some(Err(_err)) => rsx! { "Failed to scan" },
|
||||
Some(Ok(peripherals)) => rsx! {
|
||||
|
||||
@@ -53,7 +53,7 @@ fn app() -> Element {
|
||||
}
|
||||
}
|
||||
div {
|
||||
match breed.result() {
|
||||
match breed.value() {
|
||||
None => rsx! { div { "Click the button to fetch a dog!" } },
|
||||
Some(Err(_e)) => rsx! { div { "Failed to fetch a dog, please try again." } },
|
||||
Some(Ok(res)) => rsx! {
|
||||
|
||||
@@ -69,13 +69,13 @@ fn app() -> Element {
|
||||
}
|
||||
button {
|
||||
class: "button is-primary",
|
||||
class: if image.is_pending() { "is-loading" },
|
||||
class: if image.pending() { "is-loading" },
|
||||
onclick: move |_| {
|
||||
image.call();
|
||||
},
|
||||
"Generate image"
|
||||
}
|
||||
if let Some(image) = image.value() {
|
||||
if let Some(Ok(image)) = image.value() {
|
||||
for image in image.read().data.as_slice() {
|
||||
section { class: "is-flex",
|
||||
div { class: "container is-fluid",
|
||||
|
||||
@@ -20,7 +20,7 @@ fn app() -> Element {
|
||||
let current_weather = use_loader(move || get_weather(country()));
|
||||
|
||||
rsx! {
|
||||
Stylesheet { href: "https://unpkg.com/tailwindcss@^2.0/dist/tailwind.min.css" }
|
||||
Stylesheet { href: asset!("/examples/assets/weatherapp.css") }
|
||||
div { class: "mx-auto p-4 bg-gray-100 h-screen flex justify-center",
|
||||
div { class: "flex items-center justify-center flex-row",
|
||||
div { class: "flex items-start justify-center flex-row",
|
||||
@@ -35,6 +35,11 @@ fn app() -> Element {
|
||||
weather: weather.cloned(),
|
||||
}
|
||||
Forecast { weather: weather.cloned() }
|
||||
div { height: "20px", margin_top: "10px",
|
||||
if weather.loading() {
|
||||
"Fetching weather data..."
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(Loading::Pending(_)) => rsx! {
|
||||
div { "Loading weather data..." }
|
||||
@@ -56,8 +61,8 @@ fn app() -> Element {
|
||||
#[component]
|
||||
fn CountryData(weather: WeatherResponse, country: WeatherLocation) -> Element {
|
||||
let today = "Today";
|
||||
let max_temp = weather.daily.temperature_2m_max.first().unwrap();
|
||||
let min_temp = weather.daily.temperature_2m_min.first().unwrap();
|
||||
let max_temp = weather.daily.temperature_2m_max[0];
|
||||
let min_temp = weather.daily.temperature_2m_min[0];
|
||||
|
||||
rsx! {
|
||||
div { class: "flex mb-4 justify-between items-center",
|
||||
@@ -82,15 +87,11 @@ fn CountryData(weather: WeatherResponse, country: WeatherLocation) -> Element {
|
||||
#[allow(non_snake_case)]
|
||||
#[component]
|
||||
fn Forecast(weather: WeatherResponse) -> Element {
|
||||
let today = (weather.daily.temperature_2m_max.first().unwrap()
|
||||
+ weather.daily.temperature_2m_max.first().unwrap())
|
||||
/ 2.0;
|
||||
let tomorrow = (weather.daily.temperature_2m_max.get(1).unwrap()
|
||||
+ weather.daily.temperature_2m_max.get(1).unwrap())
|
||||
/ 2.0;
|
||||
let past_tomorrow = (weather.daily.temperature_2m_max.get(2).unwrap()
|
||||
+ weather.daily.temperature_2m_max.get(2).unwrap())
|
||||
/ 2.0;
|
||||
let today = (weather.daily.temperature_2m_max[0] + weather.daily.temperature_2m_max[0]) / 2.0;
|
||||
let tomorrow =
|
||||
(weather.daily.temperature_2m_max[1] + weather.daily.temperature_2m_max[1]) / 2.0;
|
||||
let past_tomorrow =
|
||||
(weather.daily.temperature_2m_max[2] + weather.daily.temperature_2m_max[2]) / 2.0;
|
||||
|
||||
rsx! {
|
||||
div { class: "px-6 pt-4 relative",
|
||||
@@ -117,7 +118,7 @@ fn Forecast(weather: WeatherResponse) -> Element {
|
||||
#[component]
|
||||
fn SearchBox(mut country: WriteSignal<WeatherLocation>) -> Element {
|
||||
let mut input = use_signal(|| "".to_string());
|
||||
let locations = use_resource(move || get_locations(input()));
|
||||
let locations = use_loader(move || get_locations(input()));
|
||||
|
||||
rsx! {
|
||||
div {
|
||||
@@ -145,17 +146,32 @@ fn SearchBox(mut country: WriteSignal<WeatherLocation>) -> Element {
|
||||
}
|
||||
}
|
||||
ul { class: "bg-white border border-gray-100 w-full mt-2 max-h-72 overflow-auto",
|
||||
if let Some(Ok(locs)) = locations.read().as_ref() {
|
||||
for wl in locs.iter().take(5).cloned() {
|
||||
li { class: "pl-8 pr-2 py-1 border-b-2 border-gray-100 relative cursor-pointer hover:bg-yellow-50 hover:text-gray-900",
|
||||
onclick: move |_| country.set(wl.clone()),
|
||||
MapIcon {}
|
||||
b { "{wl.name}" }
|
||||
" · {wl.country}"
|
||||
match locations {
|
||||
Ok(locs) if locs.is_empty() => rsx! {
|
||||
li { class: "pl-8 pr-2 py-1 border-b-2 border-gray-100 relative",
|
||||
"No locations found"
|
||||
}
|
||||
},
|
||||
Ok(locs) => rsx! {
|
||||
for wl in locs.read().iter().take(5).cloned() {
|
||||
li { class: "pl-8 pr-2 py-1 border-b-2 border-gray-100 relative cursor-pointer hover:bg-yellow-50 hover:text-gray-900",
|
||||
onclick: move |_| country.set(wl.clone()),
|
||||
MapIcon {}
|
||||
b { "{wl.name}" }
|
||||
" · {wl.country}"
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(Loading::Pending(_)) => rsx! {
|
||||
li { class: "pl-8 pr-2 py-1 border-b-2 border-gray-100 relative",
|
||||
"Searching..."
|
||||
}
|
||||
},
|
||||
Err(Loading::Failed(handle)) => rsx! {
|
||||
li { class: "pl-8 pr-2 py-1 border-b-2 border-gray-100 relative",
|
||||
"Failed to search: {handle.error():?}"
|
||||
}
|
||||
}
|
||||
} else {
|
||||
"loading locations..."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -200,10 +216,11 @@ type WeatherLocations = Vec<WeatherLocation>;
|
||||
|
||||
#[derive(Debug, Default, Serialize, Deserialize)]
|
||||
struct SearchResponse {
|
||||
#[serde(default)]
|
||||
results: WeatherLocations,
|
||||
}
|
||||
|
||||
async fn get_locations(input: impl Display) -> reqwest::Result<WeatherLocations> {
|
||||
async fn get_locations(input: impl Display) -> Result<WeatherLocations> {
|
||||
let res = reqwest::get(&format!(
|
||||
"https://geocoding-api.open-meteo.com/v1/search?name={input}"
|
||||
))
|
||||
|
||||
@@ -43,8 +43,7 @@ fn Doggo() -> Element {
|
||||
}
|
||||
|
||||
reqwest::get("https://dog.ceo/api/breeds/image/random/")
|
||||
.await
|
||||
.unwrap()
|
||||
.await?
|
||||
.json::<DogApi>()
|
||||
.await
|
||||
})?;
|
||||
|
||||
@@ -105,9 +105,9 @@ fn app() -> Element {
|
||||
"Fetch User Info"
|
||||
}
|
||||
|
||||
pre { "Logged in: {login.result():?}" }
|
||||
pre { "User name: {user_name.result():?}" }
|
||||
pre { "Permissions: {permissions.result():?}" }
|
||||
pre { "Logged in: {login.value():?}" }
|
||||
pre { "User name: {user_name.value():?}" }
|
||||
pre { "Permissions: {permissions.value():?}" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ fn app() -> Element {
|
||||
}
|
||||
}
|
||||
div {
|
||||
match breed.result() {
|
||||
match breed.value() {
|
||||
None => rsx! { div { "Click the button to fetch a dog!" } },
|
||||
Some(Err(_e)) => rsx! { div { "Failed to fetch a dog, please try again." } },
|
||||
Some(Ok(res)) => rsx! { img { max_width: "500px", max_height: "500px", src: "{res}" } },
|
||||
|
||||
@@ -72,12 +72,12 @@ fn main() {
|
||||
pre { "Dog data: {dog_data.value():#?}" }
|
||||
pre { "IP data: {ip_data.value():#?}" }
|
||||
pre { "Custom encoded data: {custom_data.value():#?}" }
|
||||
pre { "Error data: {error_data.result():#?}" }
|
||||
pre { "Typed error data: {typed_error_data.result():#?}" }
|
||||
pre { "HTTP 400 data: {http_error_data.result():#?}" }
|
||||
pre { "HTTP 400 (context) data: {http_error_context_data.result():#?}" }
|
||||
pre { "Dog error data: {dog_data_err.result():#?}" }
|
||||
pre { "Throws ok data: {throws_ok_data.result():#?}" }
|
||||
pre { "Error data: {error_data.value():#?}" }
|
||||
pre { "Typed error data: {typed_error_data.value():#?}" }
|
||||
pre { "HTTP 400 data: {http_error_data.value():#?}" }
|
||||
pre { "HTTP 400 (context) data: {http_error_context_data.value():#?}" }
|
||||
pre { "Dog error data: {dog_data_err.value():#?}" }
|
||||
pre { "Throws ok data: {throws_ok_data.value():#?}" }
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -21,7 +21,7 @@ fn app() -> Element {
|
||||
rsx! {
|
||||
h1 { "Header Map Example" }
|
||||
button { onclick: move |_| headers.call(), "Get Headers" }
|
||||
if let Some(Ok(headers)) = headers.result() {
|
||||
if let Some(Ok(headers)) = headers.value() {
|
||||
p { "Response from server:" }
|
||||
pre { "{headers}" }
|
||||
} else {
|
||||
|
||||
@@ -38,7 +38,7 @@ fn app() -> Element {
|
||||
div { style: "margin: 1rem 0;",
|
||||
h2 { "Server Greeting" }
|
||||
button { onclick: move |_| message.call("World".to_string(), 30), "Get Server Greeting" }
|
||||
if message.is_pending() {
|
||||
if message.pending() {
|
||||
p { "Loading..." }
|
||||
}
|
||||
p { "{message:#?}" }
|
||||
|
||||
@@ -34,7 +34,7 @@ fn app() -> Element {
|
||||
},
|
||||
"Get Sensitive Data",
|
||||
}
|
||||
pre { "Response from locked API: {fetch_sensitive.result():?}"}
|
||||
pre { "Response from locked API: {fetch_sensitive.value():?}"}
|
||||
form {
|
||||
onsubmit: move |evt: FormEvent| async move {
|
||||
// Prevent the browser from navigating away.
|
||||
|
||||
@@ -37,7 +37,7 @@ fn app() -> Element {
|
||||
rsx! {
|
||||
h1 { "Fullstack Middleware Example" }
|
||||
button { onclick: move |_| per_route.call(), "Fetch Data" }
|
||||
pre { "{per_route.result():#?}" }
|
||||
pre { "{per_route.value():#?}" }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -174,13 +174,13 @@ fn app() -> Element {
|
||||
"Clear results"
|
||||
}
|
||||
|
||||
pre { "Echo result: {echo_action.result():#?}" }
|
||||
pre { "Chat result: {chat_action.result():#?}" }
|
||||
pre { "Dog data: {dog_data.result():#?}" }
|
||||
pre { "Custom data: {custom_data.result():#?}" }
|
||||
pre { "Anonymous: {anonymous_action.result():#?}" }
|
||||
pre { "Custom anonymous: {custom_anonymous_action.result():#?}" }
|
||||
pre { "Custom response: {custom_response_action.result():#?}" }
|
||||
pre { "Echo result: {echo_action.value():#?}" }
|
||||
pre { "Chat result: {chat_action.value():#?}" }
|
||||
pre { "Dog data: {dog_data.value():#?}" }
|
||||
pre { "Custom data: {custom_data.value():#?}" }
|
||||
pre { "Anonymous: {anonymous_action.value():#?}" }
|
||||
pre { "Custom anonymous: {custom_anonymous_action.value():#?}" }
|
||||
pre { "Custom response: {custom_response_action.value():#?}" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,13 +108,13 @@ fn app() -> Element {
|
||||
rsx! {
|
||||
div {
|
||||
button { onclick: move |_| users.call(), "Get Users" }
|
||||
pre { "{users.result():?}" }
|
||||
pre { "{users.value():?}" }
|
||||
button { onclick: move |_| messages.call(), "Get Messages" }
|
||||
pre { "{messages.result():?}" }
|
||||
pre { "{messages.value():?}" }
|
||||
button { onclick: move |_| broadcast.call(), "Broadcast Message" }
|
||||
pre { "{broadcast.result():?}" }
|
||||
pre { "{broadcast.value():?}" }
|
||||
button { onclick: move |_| add.call(), "Add Message" }
|
||||
pre { "{add.result():?}" }
|
||||
pre { "{add.value():?}" }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -36,12 +36,12 @@ fn Home() -> Element {
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn Post(id: u32) -> Element {
|
||||
fn Post(id: ReadSignal<u32>) -> Element {
|
||||
// You can return `HttpError` to return a specific HTTP status code and message.
|
||||
// `404 Not Found` will cause the server to return a 404 status code.
|
||||
//
|
||||
// `use_loader` will suspend the server-side rendering until the future resolves.
|
||||
let post_data = use_loader(move || get_post(id))?;
|
||||
let post_data = use_loader(move || get_post(id()))?;
|
||||
|
||||
rsx! {
|
||||
h1 { "Post {id}" }
|
||||
|
||||
@@ -80,7 +80,7 @@ fn app() -> Element {
|
||||
},
|
||||
"Drop files here"
|
||||
}
|
||||
pre { "{upload_as_file_upload.result():?}" }
|
||||
pre { "{upload_as_file_upload.value():?}" }
|
||||
}
|
||||
|
||||
div {
|
||||
@@ -99,9 +99,9 @@ fn app() -> Element {
|
||||
div {
|
||||
h3 { "Download a file from the server" }
|
||||
button { onclick: move |_| download_file.call(), "Download file" }
|
||||
if let Some(Ok(content)) = &download_file.result() {
|
||||
if let Some(Ok(content)) = &download_file.value() {
|
||||
pre { "{content}" }
|
||||
} else if let Some(Err(e)) = &download_file.result() {
|
||||
} else if let Some(Err(e)) = &download_file.value() {
|
||||
pre { "Error downloading file: {e}" }
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user