dioxus icon indicating copy to clipboard operation
dioxus copied to clipboard

A `use_resource` that does not run until you trigger it

Open chunleng opened this issue 9 months ago • 2 comments

Feature Request

There are cases where I want to control the resource loading to load when, say, a button click instead of when the component load. Using spawn doesn't solve this issue because spawn does not work well with SuspenseBoundary. In some sense, it'll be like use_action in leptos?

Implement Suggestion

Create a use_resource that does not get triggered with page load. Maybe some function renaming is necessary? e.g. .restart() -> .call()

chunleng avatar May 04 '25 10:05 chunleng

This looks similar to https://github.com/DioxusLabs/dioxus/issues/2539

ealmloff avatar May 04 '25 14:05 ealmloff

Thanks for linking the issue. I guess SuspenseBoundary was not considered because that issue was raise pre-0.6.0. Maybe it's time to look closer at it once again.

What I did for now is to use use_resource to communicate with SuspenseBoundary. Clunky, but will do for now.. (Following works with main branch because of https://github.com/DioxusLabs/dioxus/pull/3617)

use async_std::task::sleep;
use dioxus::prelude::*;
use std::future::Future;
use std::sync::{Arc, Mutex};
use std::time::Duration;
use tokio::sync::mpsc;

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

#[component]
fn App() -> Element {
    let mut cnt = use_signal(|| 0);
    let (mut call, action) = use_action(move || async move {
        sleep(Duration::from_secs(2)).await;
        cnt.with_mut(|x| *x += 1);
    });
    rsx! {
        SuspenseBoundary { fallback: |_| rsx! { "Loading" },
            Request { action, cnt }
        }
        " "
        button { onclick: move |_| call(), "Add" }
    }
}

#[component]
fn Request(action: Resource<()>, cnt: Signal<i32>) -> Element {
    action.suspend()?;

    rsx! { "{cnt} " }
}

fn use_action<F>(
    action_to_perform: impl FnMut() -> F + 'static,
) -> (impl FnMut() -> (), Resource<()>)
where
    F: Future<Output = ()> + 'static,
{
    let (tx, rx) = use_hook(|| {
        let (tx, rx) = mpsc::channel(1);
        (tx, Arc::new(Mutex::new(rx)))
    });
    let mut suspending_resource = use_resource(move || {
        let rx = rx.clone();
        async move {
            rx.lock().unwrap().recv().await;
        }
    });
    let tx_clone = tx.clone();
    use_hook(move || {
        spawn(async move {
            let _ = tx_clone.send(()).await;
        })
    });
    let action = Arc::new(Mutex::new(action_to_perform));
    (
        move || {
            let tx = tx.clone();
            let action = action.clone();
            spawn(async move {
                suspending_resource.restart();
                let action_to_perform = action.lock();
                if let Ok(mut action) = action_to_perform {
                    action().await;
                }
                let _ = tx.send(()).await;
            });
        },
        suspending_resource,
    )
}

chunleng avatar May 05 '25 01:05 chunleng