Search code examples
rustdioxus

How can I use max to compare Dioxus' use_state?


I'm currently using Dioxus to generate a page that dynamically changes an image carousel.

What I want to achieve right now is to use the max function to compare the result of use_state to prevent the number from coming lower from 1.

Here's a simple app:


use dioxus::{events::*, hooks::UseState, prelude::*};
use log::{info, LevelFilter};
use std::cmp::{max, Ordering};

 impl PartialOrd for UseState<i32> {
     fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
         Some(self.cmp(other))
     }
 }

 impl Ord for UseState<i32> {
     fn cmp(&self, other: &Self) -> Ordering {
         self.x.cmp(&other.x)
     }
 }

/**
 * Specify <link data-trunk rel="copy-dir" href="src/assets" />
 * in the index.html to copy the files!!
 *
 * You'll see them in the dist directory!
 */

fn main() {
    dioxus_logger::init(LevelFilter::Info).expect("failed to init logger");
    dioxus::web::launch(app);
}

fn app(cx: Scope) -> Element {
    let mut index = use_state(&cx, || 1);
    let val = index.get() as &i32;
    let num = 1 as i32;
    let change_evt = move |evt: KeyboardEvent| match evt.key.as_str() {
        "ArrowRight" => index += 1,
        "ArrowLeft" => index = max(&num.try_into(), &val.try_into()),
        _ => {}
    };

    let url = format!("/assets/manga/one_piece/1042/0{}.jpg", index);
    cx.render(rsx!(img {
            src: "{url}",
        }
        div {
            class: "display",
            onkeydown: change_evt,
            button {
                class: "but",
                onclick: move |evt| {
                    println!("{evt:?}");
                    info!("{evt:?}");
                },
                "Press me!"
            },
        },
    ))
}

I thought at the beginning that using index.get() would yield me the i32 needed to compare with max().

But it complained and told me I needed to implement PartialOrd for UseState{Integer}.

I'm currently stuck trying to implement it.

Here's my cargo.toml

[package]
name = "rust-manga"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
dioxus = { version = "0.2.4", features = ["web", "html"] }
dioxus-html = "0.2.1"
dioxus-logger = "0.3.0"
log = "0.4.17"

Solution

  • When implementing a trait for something (such as impl PartialOrd for UseState), then at least one of the two components must belong to the crate where the impl is defined. But both PartialOrd and UseState are defined outside your crate, so you cannot do that.

    There are two options:

    1. A new type that belongs to your crate, so that you can implement traits on that:
    struct MyUseState<T: 'static>(UseState<T>);
    
    impl<T> PartialOrd for MyUseState<T> {
      fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        todo!();
      }
    }
    

    The downside is that you would need to wrap instances of where you use dioxus::hooks::UseState in your custom type, at least whenever you want to compare things.

    1. Use the methods provided by UseState:

    If UseState doesn't implement the traits necessary to pass it to std::cmp::max, we have to get the inner value. It does offer multiple ways to do that, such as UseState::get(), UseState::current() or an implementation of Deref.

    So we could do max(num, *index.get()), where .get() gets a &i32, and we derefence that to get the value. Or max(num, **index), using Deref.

    However, we still can't assign that to index, as an i32 can't be assigned to an UseState.

    That's what UseState::modify() is for. It allows you to modify the value in place:

    // Again, `.modify()` only gives a reference to the closure, which we have to derefence.
    index.modify(|val| max(num, *val))