dioxus/examples/hash_fragment_state.rs
Evan Almloff 08da9a1253
Implement hash fragments in the router (#2320)
* implement hash fragments in the router

* clean up query and search example

---------

Co-authored-by: Jonathan Kelley <jkelleyrtp@gmail.com>
2024-04-25 14:30:25 -04: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() {
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]}"
}
}
}
}
}