From react to dioxus



React



Dioxus



Platform support



hot reload



About the talk



Improving todomvc on dioxus-desktop examples

https://github.com/DioxusLabs/dioxus/pull/928

Changes



Good (1)



Good (2)



Bad (1)



Bad (2)



Bad (3)



Bad (4)



Ugly (1)



Learning experience (on todomvc)



Hello world (react)

  1. yarn create react-app hello (template)
  2. yarn start
  3. Edit src/App.js
export default function App() {
  return (
    <div>
      Hello, world!
    </div>
  );
}


Hello world (dioxus web)

  1. cargo new hello
  2. cargo add dioxus dioxus-web
  3. dioxus serve --hot-reload (requires cargo install dioxus-cli)
  4. Edit src/main.rs
use dioxus::prelude::*;

fn main() {
    dioxus_web::launch(app);
}

fn app(cx: Scope) -> Element {
    cx.render(rsx! {
        div {
            "Hello, world!"
        }
    })
}

To support different platform, change dioxus_web to dioxus_desktop.



Hello world (dioxus web) caveat

If I add

    ...
    let a = 1;  // <- this
    cx.render(rsx! {
    ...

Default rust-analyzer and dioxus-cli results in ~27s rebuild.

A fix is to add to .cargo/config (not documented in docs):

[build]
target = "wasm32-unknown-unknown"

Rebuild time now ~1s.



Dioxus CLI



Trying out some react footguns

https://jakelazaroff.com/words/were-react-hooks-a-mistake/

Let’s see if dioxus inherit react footguns, even though react say they did.

function CounterButton({ started, count, onClick }) {
  return <button onClick={onClick}>{started ? "Current score: " + count : "Start"}</button>;
}

...  // a bit long, see next page


class Game extends React.Component {
  state = { count: 0, started: false };

  increment() {
    this.setState({ count: this.state.count + 1 });
  }

  start() {
    if (!this.state.started) setTimeout(() => alert(`Your score was ${this.state.count}!`), 5000);
    this.setState({ started: true });
  }

  render() {
    return (
      <CounterButton
        started={this.state.started}
        count={this.state.count}
        onClick={() => {
          this.increment();
          this.start();
        }}
      />
    );
  }
}


React hooks with bug

  const [count, setCount] = useState(0);
  const [started, setStarted] = useState(false);

  function increment() {
    setCount(count + 1);
  }

  function start() {
    if (!started) setTimeout(() => alert(`Your score was ${count}!`), 5000);
    setStarted(true);
  }

  return (
    <button
      onClick={() => {
        increment();
        start();
      }}
    >
      {started ? "Current score: " + count : "Start"}
    </button>
  );


Bug-per-bug copy in dioxus

    let count = use_state(cx, || 0);
    let started = use_state(cx, || false);

    let start = || {
        cx.spawn({
            let has_started = started.to_owned();
            let count = count.to_owned(); // this is obvious that variable will not change
            started.set(true);
            async move {
                if !has_started {
                    let alert = move || gloo_dialogs::alert(&format!("Your score was {count}!"));
                    gloo_timers::callback::Interval::new(5_000, alert).forget();
                }
            }
        });
    };

    cx.render(rsx! {
        button {
            onclick: move |_event| {
                *count.make_mut() += 1;
                start();
            },
            // format is needed as {count} does not seemed to work in `if` within content
            if **started { format!("Current score: {count}") } else { "Start".to_string() }
        }
    })


Bug-per-bug copy in dioxus

Working backword from correct solution. Need to change mental model.

    let count = use_state(cx, || 0);
    let started = use_state(cx, || false);

    let start = || {
        if !*started.get() {
            let count = count.clone(); // this is obvious that variable will not change
            let alert = move || gloo_dialogs::alert(&format!("Your score was {count}!"));
            gloo_timers::callback::Timeout::new(5_000, alert).forget();
        }
        started.set(true);
    };

    cx.render(rsx! {
        button {
            onclick: move |_event| {
                start();
                *count.make_mut() += 1;
            },
            // format is needed as {count} does not seemed to work in `if` within content
            if **started { format!("Current score: {}", count) } else { "Start".to_string() }
        }
    })


React fixed

  const [count, setCount] = useState(0);
  const [started, setStarted] = useState(false);
  const countRef = useRef(count);

  function increment() {
    setCount(count + 1);
    countRef.current = count + 1;
  }

  function start() {
    if (!started) setTimeout(() => alert(`Your score was ${countRef.current}!`), 5000);
    setStarted(true);
  }

  return (
    <button
      onClick={() => {
        increment();
        start();
      }}
    >
      {started ? "Current score: " + count : "Start"}
    </button>
  );


Dioxus fixed

    let count = use_ref(cx, || 0); // use_ref
    let started = use_state(cx, || false);

    let start = || {
        if !*started.get() {
            let count = count.clone(); // clone reference rather than value
            let alert = move || gloo_dialogs::alert(&format!("Your score was {}!", count.read()));
            gloo_timers::callback::Timeout::new(5_000, alert).forget();
        }
        started.set(true);
    };

    cx.render(rsx! {
        button {
            onclick: move |_event| {
                start();
                *count.write() += 1;
            },
            // format is needed as {count} does not seemed to work in `if` within content
            if **started { format!("Current score: {}", count.read()) } else { "Start".to_string() }
        }
    })

Looks cleaner but took me some time to figure out too.



Tokio caveat

No compile time error for tokio even when it does not support wasm.

In browser devtools,

panicked at 'time not implemented on this platform', library/std/src/sys/wasm/../unsupported/time.rs:13:9

Can replace time with gloo, but probably might not work on dioxus-desktop?

https://gloo-rs.web.app/



Take away with event count experiment



What’s missing (WIP)