Fetching Data

Our HotDog app has some basic interactivity but does not yet fetch new dog images. In this chapter, we'll interact with async and fetching data from an API.

Adding Dependencies

Dioxus does not provide any built-in utilities for fetching data. Crates like dioxus-query exist, but for this tutorial we'll implement data-fetching from scratch.

First, we need to add two new dependencies to our app: serde and reqwest.

  • Reqwest provides an HTTP client for fetching.
  • Serde will let us derive a JSON Deserializer to decode the response.

In a new terminal window, add these crates to your app with cargo add.

cargo add reqwest --features json
cargo add serde --features derive

Defining a Response Type

We'll be using the amazing dog.ceo/dog-api to fetch images of dogs for HotDog. Fortunately, the API response is quite simple to deserialize.

Let's create a new Rust struct that matches the format of the API and derive Deserialize for it.

The Dog API docs outline a sample API response:

{
    "message": "https://images.dog.ceo/breeds/leonberg/n02111129_974.jpg",
    "status": "success"
}

Our Rust struct needs to match that format, though for now we'll only include the "message" field.

#[derive(serde::Deserialize)]
struct DogApi {
	message: String,
}

Using reqwest and async

Dioxus has stellar support for asynchronous Rust. We can simply convert our onclick handler to be async and then set the img_src after the future has resolved.

The changes to our code are quite simple - just add the reqwest::get call and then call .set() on img_src with the result.

#[component]
fn DogView() -> Element {
    let mut img_src = use_signal(|| "".to_string());

    let fetch_new = move |_| async move {
        let response = reqwest::get("https://dog.ceo/api/breeds/image/random")
            .await
            .unwrap()
            .json::<DogApi>()
            .await
			.unwrap();

        img_src.set(response.message);
    };

    // ..

    rsx! {
        div { id: "dogview",
            img { src: "{img_src}" }
        }
        div { id: "buttons",
            // ..
            button { onclick: fetch_new, id: "save", "save!" }
        }
    }
}

Dioxus automatically calls dioxus::spawn on asynchronous closures. You can also use dioxus::spawn to perform async work without async closures - just call spawn() on any async block.

rsx! {
    button {
        onclick: move |_| {
            dioxus::spawn(async move {
                // do some async work...
            });
        }
    }
}

The futures passed to dioxus::spawn must not contain latent references to data outside the async block. Data that is Copy can be captured by async blocks, but all other data must be moved, usually by calling .clone().

Managing Data Fetching with use_resource

Eventually, using bare async calls might lead to race conditions and weird state bugs. For example, if the user clicks the fetch button too quickly, then two requests will be made in parallel. If the request is updating data somewhere else, the wrong request might finish early and causes a race condition.

In Dioxus, Resources are pieces of state whose value is dependent on the completion of some asynchronous work. The use_resource hook provides a Resource object with helpful methods to start, stop, pause, and modify the asynchronous state.

Let's change our component to use a resource instead:

#[component]
fn DogView() -> Element {
    let mut img_src = use_resource(|| async move {
        reqwest::get("https://dog.ceo/api/breeds/image/random")
            .await
            .unwrap()
            .json::<DogApi>()
            .await
            .unwrap()
            .message
    });

    rsx! {
        div { id: "dogview",
            img { src: img_src.cloned().unwrap_or_default() }
        }
        div { id: "buttons",
            button { onclick: move |_| img_src.restart(), id: "skip", "skip" }
            button { onclick: move |_| img_src.restart(), id: "save", "save!" }
        }
    }
}

Resources are very powerful: they integrate with Suspense, Streaming HTML, reactivity, and more.

The details of the Resource API are not terribly important right now, but you'll be using Resources frequently in larger apps, so it's a good idea to read the docs.