Dioxus
amethystate-dioxus bridges reactive state to Dioxus signals. Each field becomes a ReadSignal that re-renders only the components that read it. Handles are Copy arena-allocated indices, so they can be passed freely between components without cloning.
[dependencies]amethystate-dioxus = "*"Defining state
Section titled “Defining state”Add #[amethystate_dioxus] above #[amethystate] on each struct you want to use in components:
use amethystate_dioxus::{amethystate_dioxus, amethystate};
#[amethystate_dioxus]#[amethystate(prefix = "settings")]pub struct AppSettings { #[amestate(default = "Guest".to_string())] pub username: String,
#[amestate(default = 0)] pub counter: i32,
#[amestate(nested)] pub theme: Theme,}
#[amethystate_dioxus]#[amethystate]pub struct Theme { #[amestate(default = "light".to_string())] pub mode: String,}Provider
Section titled “Provider”Wrap your app in amethystateProvider and pass the store:
#[component]fn App() -> Element { let store = use_hook(|| { StoreBuilder::new("./app") .build() .expect("failed to open store") });
rsx! { amethystateProvider { store, Settings {} } }}Accessing state
Section titled “Accessing state”use_amethystate::<S>() returns a Handle<S> — a Copy struct with a field for each state field. Pass it down to child components as a prop:
#[component]fn Settings() -> Element { let state = use_amethystate::<AppSettings>(); // state.username, state.counter, state.theme.mode — all Copy handles}Handle<S> is a type alias that hides the generated handle name. Declare props with it:
#[component]fn ThemeEditor(settings: Handle<AppSettings>) -> Element { // ...}use_field
Section titled “use_field”Returns a (ReadSignal<T>, Callback<T>) pair for a writable field:
let (username, set_username) = use_field(state.username);
rsx! { input { value: "{username}", oninput: move |e| set_username(e.value()), }}use_read_only_field
Section titled “use_read_only_field”Returns a ReadSignal<T> for a read-only field or a lookup field without export_mut:
let host = use_read_only_field(state.host);use_pipeline
Section titled “use_pipeline”Derives a signal from one or more fields. The pipeline is registered in the arena and cleaned up when the component unmounts:
let address = use_pipeline(move || { (state.username, state.counter) .pipe() .map(|(u, c)| format!("{u}:{c}")) .dedupe()});
rsx! { p { "{address}" } }use_map
Section titled “use_map”Returns a MapSignal<K, V> for a writable ReactiveMap field. The signal holds the full snapshot and updates on any external change:
let map = use_map(state.env);
rsx! { for (k, v) in map.entries.read().clone() { div { "{k} = {v}" } } button { onclick: move |_| map.set_or_create("KEY".into(), "value".into()), "Add" } button { onclick: move |_| map.remove("KEY".into()), "Remove" }}use_map_entry
Section titled “use_map_entry”Subscribes to a single key in a ReactiveMap:
let entry = use_map_entry(state.env, "HTTP_PROXY".to_string());
rsx! { p { "{entry:?}" } }WASM / Tauri frontend
Section titled “WASM / Tauri frontend”For Tauri apps with a Dioxus WASM frontend, the provider and setup differ. The backend is a TauriBackend instead of a local store, and slices are loaded asynchronously before the app renders.
Use preload_slices! to declare which slices to load:
#[component]fn App() -> Element { let backend = TauriBackend::new();
rsx! { AmeStateProvider { backend, init: preload_slices!(AppSettings, Theme), Settings {} } }}preload_slices! suspends rendering until all slices are loaded from the Tauri backend. After that, use_amethystate::<S>() works the same as in the native case.