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