123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130 |
- //! 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]}"
- }
- }
- }
- }
- }
|