Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

use_resource.suspend() not working with SSR #3643

Open
ufoscout opened this issue Jan 24, 2025 · 3 comments
Open

use_resource.suspend() not working with SSR #3643

ufoscout opened this issue Jan 24, 2025 · 3 comments
Labels
fullstack related to the fullstack crate

Comments

@ufoscout
Copy link

Problem
The documentation of the suspend function states:

Suspend the resource's future and only continue rendering when the future is ready

but this is not true when rendering is performed on the server.

Steps To Reproduce
This code reproduces the issue:

main.rs

use dioxus::prelude::*;

fn main() {
    dioxus::launch(App);
}

#[derive(Debug, Clone)]
pub struct Session {
    pub username: String,
}

pub static SESSION: GlobalSignal<Option<Session>> = Global::new(|| None);

#[component]
fn App() -> Element {
    let session = use_resource(move || async move {
        let session = Session {
            username: "Freddie Mercury".to_string(),
        };
        *SESSION.write() = Some(session.clone());
        session
    }).suspend();

    rsx! {
        p { "Session: {session:?}" }
        p { "Global Session: {SESSION:?}" }

    }
}

Cargo.toml

[package]
name = "test_dioxus"
version = "0.1.0"
edition = "2021"

[dependencies]
dioxus = { version = "0.6.2", features = ["fullstack"] }

[features]
default = []
web = ["dioxus/web"]

When the page is loaded, the server returns:

Session: Err(Suspended(SuspendedFuture { origin: ScopeId(3, "root"), task: Task { id: DefaultKey(1v1), unsend: PhantomData<*const ()> }, placeholder: VNode { vnode: VNodeInner { key: None, template: Template { roots: [Dynamic { id: 0 }], node_paths: [[0]], attr_paths: [] }, dynamic_nodes: [Placeholder(VPlaceholder)], dynamic_attrs: [] }, mount: Cell { value: MountId(18446744073709551615) } } }))

Global Session: None

then soon after, when the client is hydrated, the page turns to:

Session: Ok(Session { username: "Freddie Mercury" })

Global Session: Some(Session { username: "Freddie Mercury" })

Expected behavior

I would expect the server to have the same rendering behavior as the client, and to directly return:

Session: Ok(Session { username: "Freddie Mercury" })

Global Session: Some(Session { username: "Freddie Mercury" })

I also tried other hook types, like use_server_future, but I could not find a clean way to have the same behavior on the server and web client which is a requirement I have for SEO.

Environment:

  • Dioxus version: 0.6.2
  • Rust version: 1.84.0
  • OS info: ubuntu 24.04
  • App platform: web
@ealmloff ealmloff added the fullstack related to the fullstack crate label Jan 24, 2025
@ealmloff
Copy link
Member

.suspend() only does something when you bubble up the suspense Error with ?. If you don't, the future may or may not run and will not block renderering. There are two other issues with your code:

  1. Resources cannot have side effects like setting SESSION. Dioxus fullstack will only serialize the result of the future to send to the client. It cannot serialize arbitrary side effects
  2. use_resource should be use_server_future in dioxus fullstack. use_server_future handles hydrating the server future. It serializes the result of the future on the server and deserializes it on the client

I am working on hydration documentation in DioxusLabs/docsite#397 which will include hydration errors like this

@ufoscout
Copy link
Author

ufoscout commented Jan 24, 2025

@ealmloff Thank you. The ? seems to work, now the server returns the expected HTML but then the client dynamically adds a duplicated line producing this weird final output:

Session: Session { username: "Freddie Mercury" }

Global Session: Some(Session { username: "Freddie Mercury" })

Global Session: Some(Session { username: "Freddie Mercury" })

BTW, is this the correct way of using the use_server_future to set a global state?

#[component]
fn App() -> Element {

    let session = use_server_future(move || async move {
        let session = Session {
            username: "Freddie Mercury".to_string(),
        };
        session
    })?;

    session.as_ref().map(|session| {
        *SESSION.write() = Some(session.clone());
    });

    rsx! {
        p { "Session: {session:?}" }
        p { "Global Session: {SESSION:?}" }
    }
}

@ealmloff
Copy link
Member

@ealmloff Thank you. The ? seems to work, now the server returns the expected HTML but then the client dynamically adds a duplicated line producing this weird final output:

Session: Session { username: "Freddie Mercury" }

Global Session: Some(Session { username: "Freddie Mercury" })

Global Session: Some(Session { username: "Freddie Mercury" })

I do get that error if you just add the ?. You need to also switch to use_server_future

BTW, is this the correct way of using the use_server_future to set a global state?

#[component]
fn App() -> Element {

let session = use_server_future(move || async move {
    let session = Session {
        username: "Freddie Mercury".to_string(),
    };
    session
})?;

session.as_ref().map(|session| {
    *SESSION.write() = Some(session.clone());
});

rsx! {
    p { "Session: {session:?}" }
    p { "Global Session: {SESSION:?}" }
}

}

Close, that code will write to SESSION every time the component runs which can cause a bunch of unnecessary reruns. You can move it into use_effect or use_memo to only write to session when the session future changes:

#![allow(non_snake_case)]
use dioxus::prelude::*;
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Session {
    pub username: String,
}

pub static SESSION: GlobalSignal<Option<Session>> = Global::new(|| None);

fn main() {
    launch(app);
}

#[component]
fn app() -> Element {
    let session = use_server_future(move || async move {
        let session = Session {
            username: "Freddie Mercury".to_string(),
        };
        session
    })?;

    use_memo(move || {
        *SESSION.write() = session(); 
    });

    rsx! {
        p { "Session: {session:?}" }
        p { "Global Session: {SESSION:?}" }
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
fullstack related to the fullstack crate
Projects
None yet
Development

No branches or pull requests

2 participants