Skip to content

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 = "*"

Add #[amethystate_framework_arena] above #[amethystate] on each struct you want to use in components:

use amethystate::amethystate;
use amethystate_macros_arena::amethystate_framework_arena;
#[amethystate_framework_arena]
#[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_framework_arena]
#[amethystate]
pub struct Theme {
#[amestate(default = "light".to_string())]
pub mode: String,
}

Wrap your app in AmeStateProvider and pass the store:

#[component]
fn App() -> Element {
let store = use_hook(|| {
StoreBuilder::new("./app")
.build()
.expect("failed to open store")
});
rsx! {
AmeStateProvider {
store,
Settings {}
}
}
}

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 {
// ...
}

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()),
}
}

Returns a ReadSignal<T> for any field, with no setter beside it - for a value the component displays and never writes:

let host = use_read_only_field(state.host);

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.insert("KEY".into(), "value".into()), "Add" }
button { onclick: move |_| map.remove("KEY".into()), "Remove" }
}

Subscribes to a single key in a ReactiveMap:

let entry = use_map_entry(state.env, "HTTP_PROXY".to_string());
rsx! { p { "{entry:?}" } }

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.