Skip to main content

freya/_docs/
remote_data.rs

1//! # Remote Data
2//!
3//! Freya Query provides async data management for Freya applications,
4//! handling caching, background refetching, deduplication, and automatic invalidation.
5//!
6//! It is available under the `query` feature flag:
7//!
8//! ```toml
9//! [dependencies]
10//! freya = { version = "...", features = ["query"] }
11//! ```
12//!
13//! It manages two types of operations:
14//!
15//! - **Queries** ([`use_query`](crate::query::use_query)): Read operations that fetch, cache, and reactively share data.
16//! - **Mutations** ([`use_mutation`](crate::query::use_mutation)): Write operations that modify data and can invalidate queries.
17//!
18//! ## Queries
19//!
20//! ### Defining a query
21//!
22//! Implement [`QueryCapability`](crate::query::QueryCapability) on a type to define how data is fetched:
23//!
24//! ```rust,no_run
25//! # use freya::prelude::*;
26//! # use freya::query::*;
27//! #[derive(Clone, PartialEq, Hash, Eq)]
28//! struct FetchUser;
29//!
30//! impl QueryCapability for FetchUser {
31//!     type Ok = String;
32//!     type Err = String;
33//!     /// The input parameter for the query.
34//!     type Keys = u32;
35//!
36//!     async fn run(&self, user_id: &Self::Keys) -> Result<Self::Ok, Self::Err> {
37//!         // Fetch from an API, database, etc.
38//!         Ok(format!("User {user_id}"))
39//!     }
40//! }
41//! ```
42//!
43//! ### Using a query in a component
44//!
45//! Call [`use_query`](crate::query::use_query) with a [`Query`](crate::query::Query) to subscribe
46//! a component to cached, reactive data:
47//!
48//! ```rust,no_run
49//! # use freya::prelude::*;
50//! # use freya::query::*;
51//! # #[derive(Clone, PartialEq, Hash, Eq)]
52//! # struct FetchUser;
53//! # impl QueryCapability for FetchUser {
54//! #     type Ok = String;
55//! #     type Err = String;
56//! #     type Keys = u32;
57//! #     async fn run(&self, user_id: &Self::Keys) -> Result<Self::Ok, Self::Err> {
58//! #         Ok(format!("User {user_id}"))
59//! #     }
60//! # }
61//! #[derive(PartialEq)]
62//! struct UserProfile(u32);
63//!
64//! impl Component for UserProfile {
65//!     fn render(&self) -> impl IntoElement {
66//!         let query = use_query(Query::new(self.0, FetchUser));
67//!
68//!         match &*query.read().state() {
69//!             QueryStateData::Pending => "Loading...".to_string(),
70//!             QueryStateData::Loading { res } => {
71//!                 format!("Refreshing... Previous: {:?}", res)
72//!             }
73//!             QueryStateData::Settled { res, .. } => {
74//!                 format!("Result: {:?}", res)
75//!             }
76//!         }
77//!     }
78//! }
79//! ```
80//!
81//! Multiple components using the same query (same capability type + same keys) share the
82//! same cache entry. The query only runs once and all subscribers receive the result.
83//!
84//! ### Reading query state
85//!
86//! [`UseQuery`](crate::query::UseQuery) gives access to the query state, see its docs for
87//! the full API. The state is exposed as [`QueryStateData`](crate::query::QueryStateData).
88//!
89//! ### Query configuration
90//!
91//! [`Query`](crate::query::Query) supports builder methods to control caching behavior.
92//! See its docs for the full list of options (`stale_time`, `clean_time`, `interval_time`, `enable`).
93//!
94//! ```rust,no_run
95//! # use freya::prelude::*;
96//! # use freya::query::*;
97//! # use std::time::Duration;
98//! # #[derive(Clone, PartialEq, Hash, Eq)]
99//! # struct FetchUser;
100//! # impl QueryCapability for FetchUser {
101//! #     type Ok = String;
102//! #     type Err = String;
103//! #     type Keys = u32;
104//! #     async fn run(&self, _: &Self::Keys) -> Result<Self::Ok, Self::Err> { Ok(String::new()) }
105//! # }
106//! # #[derive(PartialEq)]
107//! # struct Example;
108//! # impl Component for Example {
109//! #     fn render(&self) -> impl IntoElement {
110//! let user = use_query(
111//!     Query::new(1, FetchUser)
112//!         .stale_time(Duration::from_secs(300))
113//!         .clean_time(Duration::from_secs(600))
114//!         .interval_time(Duration::from_secs(30))
115//!         .enable(true),
116//! );
117//! # rect()
118//! #     }
119//! # }
120//! ```
121//!
122//! ### Invalidating queries
123//!
124//! You can manually trigger a re-fetch from a [`UseQuery`](crate::query::UseQuery):
125//!
126//! ```rust,ignore
127//! // Fire-and-forget (spawns a background task)
128//! user.invalidate();
129//!
130//! // Await the result
131//! let reader = user.invalidate_async().await;
132//! ```
133//!
134//! For broader invalidation, use [`QueriesStorage`](crate::query::QueriesStorage):
135//!
136//! ```rust,ignore
137//! // Re-run ALL FetchUser queries
138//! QueriesStorage::<FetchUser>::invalidate_all().await;
139//!
140//! // Re-run only FetchUser queries matching specific keys
141//! QueriesStorage::<FetchUser>::invalidate_matching(user_id).await;
142//! ```
143//!
144//! `invalidate_matching` calls the [`matches()`](crate::query::QueryCapability::matches) method on each
145//! cached query. By default `matches()` returns `true` (all queries match). Override it for selective invalidation:
146//!
147//! ```rust,no_run
148//! # use freya::prelude::*;
149//! # use freya::query::*;
150//! #[derive(Clone, PartialEq, Hash, Eq)]
151//! struct FetchUser {
152//!     user_id: u32,
153//! }
154//!
155//! impl QueryCapability for FetchUser {
156//!     type Ok = String;
157//!     type Err = String;
158//!     type Keys = u32;
159//!
160//!     async fn run(&self, user_id: &Self::Keys) -> Result<Self::Ok, Self::Err> {
161//!         Ok(format!("User {user_id}"))
162//!     }
163//!
164//!     fn matches(&self, keys: &Self::Keys) -> bool {
165//!         // Only invalidate if the user_id matches
166//!         &self.user_id == keys
167//!     }
168//! }
169//! ```
170//!
171//! ### Standalone queries
172//!
173//! To run a query outside of a component (e.g. from an async task), use
174//! [`QueriesStorage::get()`](crate::query::QueriesStorage::get) with a [`GetQuery`](crate::query::GetQuery):
175//!
176//! ```rust,ignore
177//! let reader = QueriesStorage::<FetchUser>::get(
178//!     GetQuery::new(user_id, FetchUser)
179//!         .stale_time(Duration::from_secs(60))
180//! ).await;
181//! ```
182//!
183//! ## Mutations
184//!
185//! ### Defining a mutation
186//!
187//! Implement [`MutationCapability`](crate::query::MutationCapability) to define a write operation:
188//!
189//! ```rust,no_run
190//! # use freya::prelude::*;
191//! # use freya::query::*;
192//! # #[derive(Clone, PartialEq, Hash, Eq)]
193//! # struct FetchUser;
194//! # impl QueryCapability for FetchUser {
195//! #     type Ok = String;
196//! #     type Err = String;
197//! #     type Keys = u32;
198//! #     async fn run(&self, _: &Self::Keys) -> Result<Self::Ok, Self::Err> { Ok(String::new()) }
199//! # }
200//! #[derive(Clone, PartialEq, Hash, Eq)]
201//! struct UpdateUser {
202//!     user_id: u32,
203//! }
204//!
205//! impl MutationCapability for UpdateUser {
206//!     type Ok = ();
207//!     type Err = String;
208//!     /// (field_name, new_value)
209//!     type Keys = (String, String);
210//!
211//!     async fn run(&self, keys: &Self::Keys) -> Result<Self::Ok, Self::Err> {
212//!         // Send update to the API
213//!         Ok(())
214//!     }
215//!
216//!     /// Called after `run()` completes. Use this to invalidate related queries.
217//!     async fn on_settled(&self, _keys: &Self::Keys, result: &Result<Self::Ok, Self::Err>) {
218//!         if result.is_ok() {
219//!             QueriesStorage::<FetchUser>::invalidate_matching(self.user_id).await;
220//!         }
221//!     }
222//! }
223//! ```
224//!
225//! The [`on_settled`](crate::query::MutationCapability::on_settled) callback is the primary mechanism for
226//! keeping query data consistent after mutations. It runs after `run()` regardless of success or failure.
227//!
228//! ### Using a mutation in a component
229//!
230//! Call [`use_mutation`](crate::query::use_mutation) with a [`Mutation`](crate::query::Mutation):
231//!
232//! ```rust,no_run
233//! # use freya::prelude::*;
234//! # use freya::query::*;
235//! # #[derive(Clone, PartialEq, Hash, Eq)]
236//! # struct UpdateUser { user_id: u32 }
237//! # impl MutationCapability for UpdateUser {
238//! #     type Ok = ();
239//! #     type Err = String;
240//! #     type Keys = (String, String);
241//! #     async fn run(&self, _: &Self::Keys) -> Result<Self::Ok, Self::Err> { Ok(()) }
242//! # }
243//! #[derive(PartialEq)]
244//! struct UserEditor(u32);
245//!
246//! impl Component for UserEditor {
247//!     fn render(&self) -> impl IntoElement {
248//!         let mutation = use_mutation(Mutation::new(UpdateUser { user_id: self.0 }));
249//!
250//!         let status = match &*mutation.read().state() {
251//!             MutationStateData::Pending => "Ready",
252//!             MutationStateData::Loading { .. } => "Saving...",
253//!             MutationStateData::Settled { res, .. } if res.is_ok() => "Saved!",
254//!             MutationStateData::Settled { .. } => "Error",
255//!         };
256//!
257//!         rect().child(status).child(
258//!             Button::new()
259//!                 .on_press(move |_| mutation.mutate(("name".to_string(), "Alice".to_string())))
260//!                 .child("Save"),
261//!         )
262//!     }
263//! }
264//! ```
265//!
266//! See [`UseMutation`](crate::query::UseMutation) docs for the full API. The state is exposed
267//! as [`MutationStateData`](crate::query::MutationStateData).
268//!
269//! [`Mutation`](crate::query::Mutation) also supports builder methods, see its docs.
270//!
271//! ## Captured values
272//!
273//! Query and mutation types must implement `PartialEq` and `Hash` since they are used as cache keys.
274//! This is a problem for values like API clients or `State<T>` handles that should not affect cache identity.
275//!
276//! [`Captured<T>`](crate::query::Captured) wraps a value so it is invisible to caching:
277//! its `PartialEq` always returns `true` and its `Hash` is a no-op.
278//!
279//! ```rust,no_run
280//! # use freya::prelude::*;
281//! # use freya::query::*;
282//! #[derive(Clone, PartialEq, Hash, Eq)]
283//! struct FetchTodos(Captured<State<DbClient>>);
284//!
285//! # #[derive(Clone)]
286//! # struct DbClient;
287//! impl QueryCapability for FetchTodos {
288//!     type Ok = Vec<String>;
289//!     type Err = String;
290//!     type Keys = ();
291//!
292//!     async fn run(&self, _keys: &Self::Keys) -> Result<Self::Ok, Self::Err> {
293//!         let _client: &State<DbClient> = &self.0;
294//!         Ok(vec![])
295//!     }
296//! }
297//! ```
298//!
299//! `Captured<T>` implements `Deref<Target = T>` and `DerefMut`, so you can use the inner
300//! value transparently.
301//!
302//! ## Examples
303//!
304//! - [`state_query.rs`](https://github.com/marc2332/freya/tree/main/examples/state_query.rs) - Basic query usage
305//! - [`state_mutation.rs`](https://github.com/marc2332/freya/tree/main/examples/state_mutation.rs) - Query + mutation with invalidation
306//! - [`hackernews.rs`](https://github.com/marc2332/freya/tree/main/examples/hackernews.rs) - Fetching from a real API with stale times
307//! - [`state_query_sqlite/`](https://github.com/marc2332/freya/tree/main/examples/state_query_sqlite) - Full CRUD app with SQLite