dioxus/examples/hash_fragment_state.rs
Evan Almloff 20d146d9bd
Simplify the launch builder (#2967)
* improve documentation for the fullstack server context

* Add a section about axum integration to the crate root docs

* make serve_dioxus_application accept the cfg builder directly

* remove unused server_fn module

* improve fullstack config docs

* improve documentation for the server function macro

* fix axum router extension link

* Fix doc tests

* Fix launch builder

* Simplify the launch builder

* don't re-export launch in the prelude

* refactor fullstack launch

* Fix fullstack launch builder

* Update static generation with the new builder api

* fix some formatting/overly broad launch replacements

* fix custom menu example

* fix fullstack/static generation examples

* Fix static generation launch

* A few small formatting fixes

* Fix a few doc tests

* implement LaunchConfig for serve configs

* fix fullstack launch with separate web and server launch methods

* fix check with all features

* dont expose inner core module

* clippy and check

* fix readme

---------

Co-authored-by: Jonathan Kelley <jkelleyrtp@gmail.com>
2024-10-10 16:00:58 -07:00

130 lines
3.6 KiB
Rust

//! This example shows how to use the hash segment to store state in the url.
//!
//! You can set up two way data binding between the url hash and signals.
//!
//! Run this example on desktop with
//! ```sh
//! dx serve --example hash_fragment_state --features=ciborium,base64
//! ```
//! Or on web with
//! ```sh
//! dx serve --platform web --features web --example hash_fragment_state --features=ciborium,base64 -- --no-default-features
//! ```
use std::{fmt::Display, str::FromStr};
use base64::engine::general_purpose::STANDARD;
use base64::Engine;
use dioxus::prelude::*;
use serde::{Deserialize, Serialize};
fn main() {
dioxus::launch(|| {
rsx! {
Router::<Route> {}
}
});
}
#[derive(Routable, Clone, Debug, PartialEq)]
#[rustfmt::skip]
enum Route {
#[route("/#:url_hash")]
Home {
url_hash: State,
},
}
// You can use a custom type with the hash segment as long as it implements Display, FromStr and Default
#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq)]
struct State {
counters: Vec<usize>,
}
// Display the state in a way that can be parsed by FromStr
impl Display for State {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut serialized = Vec::new();
if ciborium::into_writer(self, &mut serialized).is_ok() {
write!(f, "{}", STANDARD.encode(serialized))?;
}
Ok(())
}
}
enum StateParseError {
DecodeError(base64::DecodeError),
CiboriumError(ciborium::de::Error<std::io::Error>),
}
impl std::fmt::Display for StateParseError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::DecodeError(err) => write!(f, "Failed to decode base64: {}", err),
Self::CiboriumError(err) => write!(f, "Failed to deserialize: {}", err),
}
}
}
// Parse the state from a string that was created by Display
impl FromStr for State {
type Err = StateParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let decompressed = STANDARD
.decode(s.as_bytes())
.map_err(StateParseError::DecodeError)?;
let parsed = ciborium::from_reader(std::io::Cursor::new(decompressed))
.map_err(StateParseError::CiboriumError)?;
Ok(parsed)
}
}
#[component]
fn Home(url_hash: ReadOnlySignal<State>) -> Element {
// The initial state of the state comes from the url hash
let mut state = use_signal(&*url_hash);
// Change the state signal when the url hash changes
use_memo(move || {
if *state.peek() != *url_hash.read() {
state.set(url_hash());
}
});
// Change the url hash when the state changes
use_memo(move || {
if *state.read() != *url_hash.peek() {
navigator().replace(Route::Home { url_hash: state() });
}
});
rsx! {
button {
onclick: move |_| state.write().counters.clear(),
"Reset"
}
button {
onclick: move |_| {
state.write().counters.push(0);
},
"Add Counter"
}
for counter in 0..state.read().counters.len() {
div {
button {
onclick: move |_| {
state.write().counters.remove(counter);
},
"Remove"
}
button {
onclick: move |_| {
state.write().counters[counter] += 1;
},
"Count: {state.read().counters[counter]}"
}
}
}
}
}