Fix fullstack serialization order for adjacent components (#4600)

* fix serialization order

* add regression test

* split out test into a seperate package

* revert logging

* change order

* opinions on structure

* small wait in hydration order test

---------

Co-authored-by: Jonathan Kelley <jkelleyrtp@gmail.com>
This commit is contained in:
Evan AlmloffandJonathan Kelley authored and GitHub committed 2025-10-03 19:23:34 -07:00
1 parent ecec70f5e8
commit 12fab0f9f7
8 files changed
+215 -15

No files matched your search

@@ -0,0 +1,17 @@
// @ts-check
const { test, expect } = require("@playwright/test");
test("hydration", async ({ page }) => {
await page.goto("http://localhost:7979");
// give time for the page to load and hydrate
await page.waitForTimeout(2000);
// Expect the page to contain a button
const button = page.locator("#counter");
await expect(button).toContainText("Count 0");
// Hydration should succeed and clicking the button should increase the count
await button.click();
await expect(button).toContainText("Count 1");
});
@@ -0,0 +1,17 @@
[package]
name = "dioxus-playwright-fullstack-hydration-order-test"
version = "0.1.0"
edition = "2021"
publish = false
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
dioxus = { workspace = true, features = ["fullstack"] }
serde = "1.0.219"
tokio = { workspace = true, features = ["full"], optional = true }
[features]
default = []
server = ["dioxus/server", "dep:tokio"]
web = ["dioxus/web"]
@@ -0,0 +1,70 @@
// Adjacent server components that both use server functions shouldn't cause
// hydration issues
// https://github.com/DioxusLabs/dioxus/issues/4595
use dioxus::prelude::*;
fn main() {
dioxus::launch(|| rsx! { Home {} });
}
#[component]
pub fn Home() -> Element {
let mut count = use_signal(|| 0);
rsx! {
div {
MyStrings {}
MyFloats {}
}
button {
id: "counter",
onclick: move |_| count += 1,
"Count {count}"
}
}
}
#[component]
fn MyStrings() -> Element {
let strings = use_server_future(get_strings)?;
let data = match &*strings.read() {
Some(Ok(data)) => data.clone(),
_ => vec![],
};
rsx! {
div {
for string in data.iter() {
p { "{string}" }
}
}
}
}
#[get("/api/get_strings")]
pub async fn get_strings() -> Result<Vec<String>, ServerFnError> {
let data: Vec<String> = vec!["Hello".to_string(), "World".to_string()];
Ok(data)
}
#[component]
fn MyFloats() -> Element {
let floats = use_server_future(get_floats)?;
let data = match &*floats.read() {
Some(Ok(data)) => data.clone(),
_ => vec![],
};
rsx! {
div {
for float in data.iter() {
p { "{float}" }
}
}
}
}
#[get("/api/get_floats")]
pub async fn get_floats() -> Result<Vec<f32>, ServerFnError> {
let data: Vec<f32> = vec![1.0, 2.0, 3.0];
Ok(data)
}
+4 -4
View File
@@ -104,10 +104,10 @@ test("assets cache correctly", async ({ page }) => {
// Wait for the nested asset image to be loaded
const nestedAssetImageResponse = await nestedAssetImageFuture;
console.log(
"Nested Asset Image Response:",
nestedAssetImageResponse
);
// console.log(
// "Nested Asset Image Response:",
// nestedAssetImageResponse
// );
// Make sure the nested asset image cache control header does not contain immutable
const nestedAssetCacheControl =
nestedAssetImageResponse.headers()["cache-control"];
@@ -144,6 +144,15 @@ module.exports = defineConfig({
reuseExistingServer: !process.env.CI,
stdout: "pipe",
},
{
cwd: path.join(process.cwd(), "fullstack-hydration-order"),
command:
'cargo run --package dioxus-cli --release -- run --force-sequential --web --addr "127.0.0.1" --port 7979',
port: 7979,
timeout: 50 * 60 * 1000,
reuseExistingServer: !process.env.CI,
stdout: "pipe",
},
{
cwd: path.join(process.cwd(), "cli-optimization"),
// Remove the cache folder for the cli-optimization build to force a full cache reset