### Run Example Source: https://github.com/marc2332/dioxus-query/blob/main/README.md Execute the hello_world example provided with dioxus-query. ```bash cargo run --example hello_world ``` -------------------------------- ### Complete Dioxus Query Application Example Source: https://context7.com/marc2332/dioxus-query/llms.txt This example demonstrates a full Dioxus application integrating queries and mutations. It includes shared state management, cache configuration, and automatic query invalidation after mutations, showcasing a robust data fetching and updating pattern. ```rust use dioxus::prelude::* use dioxus_query::prelude::* use std::{cell::RefCell, rc::Rc, time::Duration}; use tokio::time::sleep; fn main() { launch(app); } // Shared client with mutable state #[derive(Clone, Default)] struct UserClient(Rc>); impl PartialEq for UserClient { fn eq(&self, other: &Self) -> bool { Rc::ptr_eq(&self.0, &other.0) } } impl Eq for UserClient {} impl UserClient { pub fn age(&self) -> i32 { *self.0.borrow() } pub fn set_age(&self, new_age: i32) { *self.0.borrow_mut() = new_age; } } // Query: Fetch user age #[derive(Clone, PartialEq, Hash, Eq)] struct GetUserAge(Captured); impl QueryCapability for GetUserAge { type Ok = i32; type Err = (); type Keys = usize; async fn run(&self, user_id: &Self::Keys) -> Result { sleep(Duration::from_millis(500)).await; match user_id { 0 => Ok(self.0.age()), _ => Err(()), } } } // Mutation: Update user age #[derive(Clone, PartialEq, Hash, Eq)] struct SetUserAge(Captured); impl MutationCapability for SetUserAge { type Ok = i32; type Err = (); type Keys = usize; async fn run(&self, user_id: &Self::Keys) -> Result { sleep(Duration::from_millis(400)).await; let new_age = self.0.age() + 1; self.0.set_age(new_age); match user_id { 0 => Ok(new_age), _ => Err(()), } } async fn on_settled(&self, user_id: &Self::Keys, _result: &Result) { // Automatically refresh the age query after mutation QueriesStorage::::invalidate_matching(*user_id).await; } } #[component] fn UserAge(id: usize) -> Element { let client = use_context::(); let user_age = use_query( Query::new(id, GetUserAge(Captured(client))) .stale_time(Duration::from_secs(4)) ); rsx!( p { "User {id} Age: {user_age.read().state():?}" } ) } fn app() -> Element { let client = use_context_provider(UserClient::default); let set_age_mutation = use_mutation(Mutation::new(SetUserAge(Captured(client)))); let increase_age = move |_| async move { set_age_mutation.mutate_async(0).await; }; rsx!( UserAge { id: 0 } UserAge { id: 0 } // Multiple subscribers share the same cached data button { onclick: increase_age, "Increase Age" } p { "Mutation state: {set_age_mutation.read().state():?}" } ) } ``` -------------------------------- ### Install Dioxus Query Source: https://github.com/marc2332/dioxus-query/blob/main/README.md Add the dioxus-query crate to your project's dependencies using Cargo. ```bash cargo add dioxus-query ``` -------------------------------- ### Dioxus Query Example Source: https://github.com/marc2332/dioxus-query/blob/main/README.md Demonstrates fetching and displaying user data with caching and manual refresh capabilities. Includes a custom query implementation and a component to display user information. ```rust #[derive(Clone)] struct FancyClient; impl FancyClient { pub fn name(&self) -> &'static str { "Marc" } } #[derive(Clone, PartialEq, Hash, Eq)] struct GetUserName(Captured); impl QueryCapability for GetUserName { type Ok = String; type Err = (); type Keys = usize; async fn run(&self, user_id: &Self::Keys) -> Result { println!("Fetching name of user {user_id}"); sleep(Duration::from_millis(650)).await; match user_id { 0 => Ok(self.0.name().to_string()), _ => Err(()), } } } #[allow(non_snake_case)] #[component] fn User(id: usize) -> Element { let user_name = use_query(Query::new(id, GetUserName(Captured(FancyClient)))); rsx!( p { "{user_name.read().state():?}" } ) } fn app() -> Element { let refresh = move |_| async move { QueriesStorage::::invalidate_matching(0).await; }; rsx!( User { id: 0 } User { id: 0 } button { onclick: refresh, label { "Refresh" } } ) } ``` -------------------------------- ### Compose Queries with GetQuery Source: https://context7.com/marc2332/dioxus-query/llms.txt Fetch query results from within other queries using `QueriesStorage::get()` and `GetQuery`. This enables data dependency patterns and query composition. Configure `stale_time` and `clean_time` for cached data. ```rust use dioxus::prelude::*; use dioxus_query::prelude::*; use std::time::Duration; use tokio::time::sleep; #[derive(Clone)] struct FancyClient; impl FancyClient { pub fn name(&self) -> &'static str { "Marc" } pub fn age(&self) -> u8 { 25 } } #[derive(Clone, PartialEq, Hash, Eq)] struct GetUserName(Captured); impl QueryCapability for GetUserName { type Ok = String; type Err = (); type Keys = usize; async fn run(&self, user_id: &Self::Keys) -> Result { sleep(Duration::from_millis(650)).await; match user_id { 0 => Ok(self.0.name().to_string()), _ => Err(()), } } } // Composite query that depends on GetUserName #[derive(Clone, PartialEq, Hash, Eq)] struct GetUserInfo(Captured); impl QueryCapability for GetUserInfo { type Ok = (String, u8); type Err = (); type Keys = usize; async fn run(&self, user_id: &Self::Keys) -> Result { // Fetch the user name from another query (with its own caching) let name_result = QueriesStorage::get( GetQuery::new(*user_id, GetUserName(self.0.clone())) .stale_time(Duration::from_secs(30)) .clean_time(Duration::from_secs(30)) ).await; // Get the settled result let name = name_result.as_settled().clone()?; // Fetch additional data sleep(Duration::from_millis(500)).await; match user_id { 0 => Ok((name, self.0.age())), _ => Err(()), } } } #[component] fn UserInfo(id: usize) -> Element { let user_info = use_query( Query::new(id, GetUserInfo(Captured(FancyClient))) .stale_time(Duration::from_secs(10)) ); rsx!( p { "{user_info.read().state():?}" } ) } fn app() -> Element { let refresh = move |_| async move { QueriesStorage::::invalidate_matching(0).await; }; rsx!( UserInfo { id: 0 } button { onclick: refresh, "Refresh" } ) } ``` -------------------------------- ### Use use_query Hook for Data Fetching Source: https://context7.com/marc2332/dioxus-query/llms.txt Subscribe a component to a query using the `use_query` hook. It automatically fetches data, caches results, and re-renders on state changes. Configure stale time, clean time, and interval refetching. ```rust use dioxus::prelude::*; use dioxus_query::prelude::*; use std::time::Duration; #[derive(Clone, PartialEq, Hash, Eq)] struct GetUserName; impl QueryCapability for GetUserName { type Ok = String; type Err = (); type Keys = usize; async fn run(&self, user_id: &Self::Keys) -> Result { Ok(format!("User {}", user_id)) } } #[component] fn User(id: usize) -> Element { // Subscribe to query with configuration let user_name = use_query( Query::new(id, GetUserName) .stale_time(Duration::from_secs(5)) // Data considered fresh for 5 seconds .clean_time(Duration::from_secs(300)) // Cache cleared after 5 minutes with no subscribers .interval_time(Duration::from_secs(15)) // Auto-refetch every 15 seconds ); // Read the current state (subscribes to changes) let state = user_name.read().state(); rsx!( match &*state { QueryStateData::Pending => rsx!(p { "Initializing..." }), QueryStateData::Loading { res: None } => rsx!(p { "Loading..." }), QueryStateData::Loading { res: Some(Ok(name)) } => rsx!(p { "Refreshing: {name}" }), QueryStateData::Loading { res: Some(Err(_)) } => rsx!(p { "Retrying after error..." }), QueryStateData::Settled { res: Ok(name), .. } => rsx!(p { "Name: {name}" }), QueryStateData::Settled { res: Err(_), .. } => rsx!(p { "Error loading user" }), } ) } fn app() -> Element { rsx!( User { id: 0 } User { id: 1 } ) } ``` -------------------------------- ### Check Query State in Dioxus Source: https://context7.com/marc2332/dioxus-query/llms.txt Use state checking methods like is_pending(), is_loading(), is_ok(), and is_err() to determine the current status of a query. This is useful for conditionally rendering UI elements based on the query's progress or result. ```rust use dioxus::prelude::* use dioxus_query::prelude::* use std::time::Duration; #[derive(Clone, PartialEq, Hash, Eq)] struct FetchData; impl QueryCapability for FetchData { type Ok = String; type Err = String; type Keys = (); async fn run(&self, _: &Self::Keys) -> Result { Ok("Hello World".to_string()) } } #[component] fn DataDisplay() -> Element { let query = use_query(Query::new((), FetchData).stale_time(Duration::from_secs(30))); let state = query.read().state(); // State checking methods let status = if state.is_pending() { "Pending - hasn t started yet" } else if state.is_loading() { "Loading - fetching data" } else if state.is_ok() { "Success - data available" } else if state.is_err() { "Error - request failed" } else { "Unknown" }; // Check if data is stale (needs refresh) let freshness = if state.is_stale(&Query::new((), FetchData).stale_time(Duration::from_secs(30))) { "Stale" } else { "Fresh" }; // Safe value extraction with ok() let value = state.ok().map(|v| v.as_str()).unwrap_or("No data"); rsx!( p { "Status: {status}" } p { "Freshness: {freshness}" } p { "Value: {value}" } ) } ``` -------------------------------- ### Configure Query Caching Behavior Source: https://context7.com/marc2332/dioxus-query/llms.txt Use builder methods on the Query struct to set stale time, clean time, and interval time for caching. The `enable` method conditionally controls query execution. ```rust use dioxus::prelude::*; use dioxus_query::prelude::*; use std::time::Duration; #[derive(Clone, PartialEq, Hash, Eq)] struct FetchData; impl QueryCapability for FetchData { type Ok = String; type Err = (); type Keys = String; async fn run(&self, key: &Self::Keys) -> Result { Ok(format!("Data for {}", key)) } } #[component] fn DataViewer(enabled: bool) -> Element { let data = use_query( Query::new("my-key".to_string(), FetchData) // Data is fresh for 10 seconds, won't refetch if another subscriber mounts .stale_time(Duration::from_secs(10)) // Keep data cached for 5 minutes after last subscriber unmounts .clean_time(Duration::from_secs(300)) // Automatically refetch every 30 seconds in background .interval_time(Duration::from_secs(30)) // Conditionally enable/disable the query .enable(enabled) ); rsx!( p { "{data.read().state():?}" } ) } ``` -------------------------------- ### Integrate Dioxus Query with Suspense Source: https://context7.com/marc2332/dioxus-query/llms.txt Use the `suspend()` method to pause component rendering until a query settles. This integrates with `SuspenseBoundary` for declarative loading UI. The `suspend()` method returns a `Result, RenderError>`, allowing suspension to propagate. ```rust use dioxus::prelude::*; use dioxus_query::prelude::*; use std::time::Duration; use tokio::time::sleep; #[derive(Clone)] struct FancyClient; impl FancyClient { pub fn name(&self) -> &'static str { "Marc" } } #[derive(Clone, PartialEq, Hash, Eq)] struct GetUserName(Captured); impl QueryCapability for GetUserName { type Ok = String; type Err = (); type Keys = usize; async fn run(&self, user_id: &Self::Keys) -> Result { sleep(Duration::from_millis(650)).await; match user_id { 0 => Ok(self.0.name().to_string()), _ => Err(()), } } } #[component] fn User(id: usize) -> Element { // suspend() returns Result, RenderError> // Use ? to propagate suspension to SuspenseBoundary let user_name = use_query(Query::new(id, GetUserName(Captured(FancyClient)))).suspend()?; // Only renders when query is settled rsx!( p { "User: {user_name:?}" } ) } fn app() -> Element { let refresh = move |_| async move { QueriesStorage::::invalidate_matching(0).await; }; rsx!( SuspenseBoundary { fallback: |_| rsx!( p { "Loading user..." } ), User { id: 0 } } button { onclick: refresh, "Refresh" } ) } ``` -------------------------------- ### Implement use_mutation Hook for State Updates Source: https://context7.com/marc2332/dioxus-query/llms.txt Use `use_mutation` to handle state changes. It supports both fire-and-forget mutations with `mutate()` and asynchronous mutations with `mutate_async()`, which allows for awaiting results and handling responses. Mutations can also trigger cache invalidations via `on_settled`. ```rust use dioxus::prelude::* use dioxus_query::prelude::* use std::time::Duration use tokio::time::sleep; #[derive(Clone, PartialEq, Hash, Eq)] struct GetUserAge; impl QueryCapability for GetUserAge { type Ok = i32; type Err = (); type Keys = usize; async fn run(&self, _user_id: &Self::Keys) -> Result { sleep(Duration::from_millis(500)).await; Ok(25) } } #[derive(Clone, PartialEq, Hash, Eq)] struct IncrementAge; impl MutationCapability for IncrementAge { type Ok = i32; type Err = (); type Keys = usize; // user_id async fn run(&self, user_id: &Self::Keys) -> Result { sleep(Duration::from_millis(400)).await; Ok(26) // Simulated new age } async fn on_settled(&self, user_id: &Self::Keys, _result: &Result) { QueriesStorage::::invalidate_matching(*user_id).await; } } #[component] fn UserProfile(user_id: usize) -> Element { let user_age = use_query(Query::new(user_id, GetUserAge)); let increment_mutation = use_mutation( Mutation::new(IncrementAge) .clean_time(Duration::from_secs(60)) // Keep mutation result cached ); // Fire-and-forget mutation let increment_sync = move |_| { increment_mutation.mutate(user_id); }; // Async mutation with result handling let increment_async = move |_| async move { let result = increment_mutation.mutate_async(user_id).await; match &*result.state() { MutationStateData::Settled { res: Ok(age), .. } => { println!("New age: {}", age); } MutationStateData::Settled { res: Err(_), .. } => { println!("Failed to increment age"); } _ => {} } }; let mutation_state = increment_mutation.read().state(); rsx!( p { "Age: {user_age.read().state():?}" } p { "Mutation: {mutation_state:?}" } button {onclick: increment_sync, "Increment (sync)"} button {onclick: increment_async, "Increment (async)"} ) } fn app() -> Element { rsx!(UserProfile { user_id: 0 }) } ``` -------------------------------- ### Implement QueryCapability Trait Source: https://context7.com/marc2332/dioxus-query/llms.txt Implement this trait for structs representing data fetching operations. It defines success/error types, keys for identification, and the async fetching logic. ```rust use dioxus_query::prelude::*; use std::time::Duration; use tokio::time::sleep; // Define a query capability for fetching user data #[derive(Clone, PartialEq, Hash, Eq)] struct GetUserName; impl QueryCapability for GetUserName { type Ok = String; // Success type type Err = (); // Error type type Keys = usize; // Key type used to identify unique queries async fn run(&self, user_id: &Self::Keys) -> Result { // Simulate API call sleep(Duration::from_millis(650)).await; match user_id { 0 => Ok("Marc".to_string()), 1 => Ok("Alice".to_string()), _ => Err(()), } } // Optional: Custom matching logic for invalidation fn matches(&self, _keys: &Self::Keys) -> bool { true // Default: matches all keys } } ``` -------------------------------- ### Invalidate Queries Programmatically Source: https://context7.com/marc2332/dioxus-query/llms.txt Use `invalidate()` for fire-and-forget invalidation, `invalidate_async()` to await completion, or `QueriesStorage::invalidate_matching()` to invalidate queries based on keys. ```rust use dioxus::prelude::*; use dioxus_query::prelude::*; use std::time::{SystemTime, UNIX_EPOCH}; #[derive(Clone, PartialEq, Hash, Eq)] struct GetTime; impl QueryCapability for GetTime { type Ok = SystemTime; type Err = (); type Keys = (); async fn run(&self, _: &Self::Keys) -> Result { Ok(SystemTime::now()) } } fn app() -> Element { let time = use_query(Query::new((), GetTime)); // Direct invalidation (fire-and-forget) let refresh_sync = move |_| { time.invalidate(); }; // Async invalidation (await completion) let refresh_async = move |_| async move { let result = time.invalidate_async().await; println!("Refreshed: {:?}", result.state()); }; // Global invalidation - invalidate all GetTime queries matching the key let refresh_all = move |_| async move { QueriesStorage::::invalidate_matching(()).await; }; // Invalidate ALL queries of this type let refresh_everything = move |_| async move { QueriesStorage::::invalidate_all().await; }; let time_display = time .read() .state() .ok() .map(|t| t.duration_since(UNIX_EPOCH)); rsx!( p { "{time_display:?}" } button { onclick: refresh_sync, "Refresh (sync)" } button { onclick: refresh_async, "Refresh (async)" } button { onclick: refresh_all, "Refresh All Matching" } ) } ``` -------------------------------- ### Implement Mutation with on_settled Callback Source: https://context7.com/marc2332/dioxus-query/llms.txt Define mutations using the `MutationCapability` trait. The `on_settled` callback can be used to automatically invalidate related queries after a mutation completes successfully. ```rust use dioxus::prelude::*; use dioxus_query::prelude::*; use std::time::Duration; use tokio::time::sleep; // Query for fetching user age #[derive(Clone, PartialEq, Hash, Eq)] struct GetUserAge; impl QueryCapability for GetUserAge { type Ok = i32; type Err = (); type Keys = usize; async fn run(&self, user_id: &Self::Keys) -> Result { sleep(Duration::from_millis(500)).await; Ok(25) // Simulated fetch } } // Mutation for updating user age #[derive(Clone, PartialEq, Hash, Eq)] struct SetUserAge; impl MutationCapability for SetUserAge { type Ok = i32; // Returns new age type Err = String; // Error message type Keys = (usize, i32); // (user_id, new_age) async fn run(&self, keys: &Self::Keys) -> Result { let (user_id, new_age) = keys; sleep(Duration::from_millis(400)).await; if *new_age < 0 { Err("Age cannot be negative".to_string()) } else { println!("Updated user {} age to {}", user_id, new_age); Ok(*new_age) } } // Automatically invalidate related queries after mutation completes async fn on_settled(&self, keys: &Self::Keys, result: &Result) { if result.is_ok() { let (user_id, _) = keys; QueriesStorage::::invalidate_matching(*user_id).await; } } } ``` -------------------------------- ### Use Captured Wrapper for Non-Comparable Values Source: https://context7.com/marc2332/dioxus-query/llms.txt The `Captured` wrapper is used to include non-comparable types, such as API clients, within query or mutation structs. It ensures that changes to these wrapped values do not trigger unnecessary query invalidations by implementing `PartialEq` to always return `true`. ```rust use dioxus::prelude::* use dioxus_query::prelude::* use std::time::Duration use tokio::time::sleep; // Simulated API client that shouldn't affect query equality #[derive(Clone)] struct ApiClient { base_url: String, } impl ApiClient { pub async fn fetch_user(&self, id: usize) -> Result { sleep(Duration::from_millis(500)).await; Ok(format!("User {} from {}", id, self.base_url)) } } // Wrap the client in Captured to exclude it from equality checks #[derive(Clone, PartialEq, Hash, Eq)] struct FetchUser(Captured); impl QueryCapability for FetchUser { type Ok = String; type Err = String; type Keys = usize; async fn run(&self, user_id: &Self::Keys) -> Result { // Access the inner client via Deref self.0.fetch_user(*user_id).await } } #[component] fn UserDisplay(id: usize) -> Element { let client = ApiClient { base_url: "https://api.example.com".to_string() }; // The query identity is based only on the Keys (user_id), not the client let user = use_query(Query::new(id, FetchUser(Captured(client)))); rsx!( p { "{user.read().state():?}" } ) } fn app() -> Element { rsx!( UserDisplay { id: 0 } UserDisplay { id: 1 } ) } ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.