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:
Jonathan Kelley authored and GitHub committed 2025-10-14 17:34:52 -07:00
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! {
+1 -1
View File
@@ -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",
+40 -23
View File
@@ -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}"
))