leptos_query_rs/infinite/
mod.rs1use crate::{
2 client::QueryClient,
3 types::QueryKey,
4 retry::RetryConfig,
5 QueryError,
6};
7use leptos::prelude::*;
8use leptos::task::spawn_local;
9use serde::{de::DeserializeOwned, Serialize, Deserialize};
10use std::{sync::Arc, future::Future};
11use crate::QueryObserverId;
12
13#[derive(Clone, Debug)]
15pub struct InfiniteQueryOptions {
16 pub retry: RetryConfig,
18 pub keep_previous_data: bool,
20 pub max_pages: Option<usize>,
22 pub refetch_on_window_focus: bool,
24 pub refetch_on_reconnect: bool,
26}
27
28impl Default for InfiniteQueryOptions {
29 fn default() -> Self {
30 Self {
31 retry: RetryConfig::default(),
32 keep_previous_data: true,
33 max_pages: Some(10),
34 refetch_on_window_focus: true,
35 refetch_on_reconnect: true,
36 }
37 }
38}
39
40#[derive(Clone)]
42pub struct InfiniteQueryOptionsBuilder {
43 options: InfiniteQueryOptions,
44}
45
46impl Default for InfiniteQueryOptionsBuilder {
47 fn default() -> Self {
48 Self::new()
49 }
50}
51
52impl InfiniteQueryOptionsBuilder {
53 pub fn new() -> Self {
54 Self {
55 options: InfiniteQueryOptions::default(),
56 }
57 }
58
59 pub fn retry(mut self, retry: RetryConfig) -> Self {
60 self.options.retry = retry;
61 self
62 }
63
64 pub fn keep_previous_data(mut self, keep: bool) -> Self {
65 self.options.keep_previous_data = keep;
66 self
67 }
68
69 pub fn max_pages(mut self, max: Option<usize>) -> Self {
70 self.options.max_pages = max;
71 self
72 }
73
74 pub fn refetch_on_window_focus(mut self, refetch: bool) -> Self {
75 self.options.refetch_on_window_focus = refetch;
76 self
77 }
78
79 pub fn refetch_on_reconnect(mut self, refetch: bool) -> Self {
80 self.options.refetch_on_reconnect = refetch;
81 self
82 }
83
84 pub fn build(self) -> InfiniteQueryOptions {
85 self.options
86 }
87}
88
89#[derive(Clone, Debug, Serialize, Deserialize)]
91pub struct PageInfo {
92 pub page: usize,
94 pub per_page: usize,
96 pub total: usize,
98 pub has_next: bool,
100 pub has_prev: bool,
102}
103
104#[derive(Clone, Debug, Serialize, Deserialize)]
106pub struct Page<T> {
107 pub data: Vec<T>,
109 pub info: PageInfo,
111}
112
113#[derive(Clone)]
115pub struct InfiniteQueryResult<T> {
116 pub pages: RwSignal<Vec<Page<T>>>,
118 pub current_page: RwSignal<usize>,
120 pub has_next: RwSignal<bool>,
122 pub has_prev: RwSignal<bool>,
124 pub is_loading: RwSignal<bool>,
126 pub error: RwSignal<Option<QueryError>>,
128 pub is_stale: RwSignal<bool>,
130 pub is_fetching: RwSignal<bool>,
132 pub key: QueryKey,
134 pub observer_id: QueryObserverId,
136 client: Arc<QueryClient>,
138}
139
140impl<T> InfiniteQueryResult<T>
141where
142 T: Clone + Serialize + DeserializeOwned + Send + Sync + 'static,
143{
144 pub async fn fetch_next_page(&self) -> Result<(), QueryError> {
146 let current_page = self.current_page.get();
147 let has_next = self.has_next.get();
148
149 if !has_next {
150 return Ok(());
151 }
152
153 self.is_loading.set(true);
155
156 let next_page = current_page + 1;
158 let result = self
159 .client
160 .fetch_infinite_page::<T>(&self.key, next_page)
161 .await?;
162
163 let result_clone = result.clone();
165 self.pages.update(|pages| {
166 if let Some(max_pages) = self.client.get_infinite_options(&self.key).max_pages {
167 if pages.len() >= max_pages {
168 pages.remove(0); }
170 }
171 pages.push(result_clone);
172 });
173
174 self.current_page.set(next_page);
176 self.has_next.set(result.info.has_next);
177
178 self.is_loading.set(false);
179 Ok(())
180 }
181
182 pub async fn fetch_previous_page(&self) -> Result<(), QueryError> {
184 let current_page = self.current_page.get();
185 let has_prev = self.has_prev.get();
186
187 if !has_prev {
188 return Ok(());
189 }
190
191 self.is_loading.set(true);
193
194 let prev_page = current_page.saturating_sub(1);
196 let result = self
197 .client
198 .fetch_infinite_page::<T>(&self.key, prev_page)
199 .await?;
200
201 let result_clone = result.clone();
203 self.pages.update(|pages| {
204 pages.insert(0, result_clone);
205
206 if let Some(max_pages) = self.client.get_infinite_options(&self.key).max_pages {
207 if pages.len() > max_pages {
208 pages.pop(); }
210 }
211 });
212
213 self.current_page.set(prev_page);
215 self.has_prev.set(result.info.has_prev);
216
217 self.is_loading.set(false);
218 Ok(())
219 }
220
221 pub async fn refetch(&self) -> Result<(), QueryError> {
223 self.is_fetching.set(true);
224
225 self.pages.set(Vec::new());
227 self.current_page.set(0);
228 self.has_next.set(true);
229 self.has_prev.set(false);
230
231 let result = self
233 .client
234 .fetch_infinite_page::<T>(&self.key, 0)
235 .await?;
236
237 let result_clone = result.clone();
239 self.pages.set(vec![result_clone]);
240 self.has_next.set(result.info.has_next);
241 self.is_stale.set(false);
242 self.is_fetching.set(false);
243
244 Ok(())
245 }
246
247 pub async fn invalidate(&self) -> Result<(), QueryError> {
249 self.refetch().await
251 }
252
253 pub async fn remove(&self) -> Result<(), QueryError> {
255 self.client.remove_query(&self.key);
256 self.pages.set(Vec::new());
257 self.current_page.set(0);
258 self.has_next.set(true);
259 self.has_prev.set(false);
260 Ok(())
261 }
262
263 pub fn get_all_data(&self) -> Vec<T> {
265 self.pages
266 .get()
267 .iter()
268 .flat_map(|page| page.data.clone())
269 .collect()
270 }
271
272 pub fn get_page_data(&self, page: usize) -> Option<Vec<T>> {
274 self.pages
275 .get()
276 .get(page)
277 .map(|page| page.data.clone())
278 }
279
280 pub fn get_total_count(&self) -> usize {
282 self.pages
283 .get()
284 .iter()
285 .map(|page| page.info.total)
286 .sum()
287 }
288}
289
290pub fn use_infinite_query<T, K, F, Fut>(
292 key_fn: impl Fn() -> K + 'static,
293 query_fn: impl Fn(usize) -> F + Clone + Send + Sync + 'static,
294 _options: InfiniteQueryOptions,
295) -> InfiniteQueryResult<T>
296where
297 T: Clone + Serialize + DeserializeOwned + Send + Sync + 'static,
298 K: Into<QueryKey>,
299 F: Future<Output = Result<Page<T>, QueryError>> + Send + 'static,
300{
301 let client = use_context::<Arc<QueryClient>>()
302 .expect("use_infinite_query must be used within QueryClientProvider");
303
304 let key = key_fn().into();
305 let observer_id = client.register_infinite_observer(&key);
306
307 let pages = RwSignal::new(Vec::new());
309 let current_page = RwSignal::new(0);
310 let has_next = RwSignal::new(true);
311 let has_prev = RwSignal::new(false);
312 let is_loading = RwSignal::new(false);
313 let error = RwSignal::new(None);
314 let is_stale = RwSignal::new(false);
315 let is_fetching = RwSignal::new(false);
316
317 spawn_local(async move {
319 is_loading.set(true);
320
321 match query_fn(0).await {
322 Ok(page) => {
323 let page_clone = page.clone();
324 pages.set(vec![page_clone]);
325 has_next.set(page.info.has_next);
326 is_stale.set(false);
327 }
328 Err(e) => {
329 error.set(Some(e));
330 }
331 }
332
333 is_loading.set(false);
334 });
335
336 InfiniteQueryResult {
337 pages,
338 current_page,
339 has_next,
340 has_prev,
341 is_loading,
342 error,
343 is_stale,
344 is_fetching,
345 key,
346 observer_id,
347 client,
348 }
349}
350
351impl InfiniteQueryOptions {
353 pub fn builder() -> InfiniteQueryOptionsBuilder {
354 InfiniteQueryOptionsBuilder::new()
355 }
356}
357
358#[cfg(test)]
359mod tests {
360 use super::*;
361
362
363 #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
364 struct TestItem {
365 id: usize,
366 name: String,
367 }
368
369 #[test]
372 fn test_infinite_query_options_builder() {
373 let options = InfiniteQueryOptions::builder()
374 .retry(RetryConfig::default())
375 .keep_previous_data(false)
376 .max_pages(Some(5))
377 .build();
378
379 assert_eq!(options.keep_previous_data, false);
380 assert_eq!(options.max_pages, Some(5));
381 }
382
383 #[test]
384 fn test_page_info() {
385 let info = PageInfo {
386 page: 1,
387 per_page: 10,
388 total: 100,
389 has_next: true,
390 has_prev: true,
391 };
392
393 assert_eq!(info.page, 1);
394 assert_eq!(info.per_page, 10);
395 assert_eq!(info.total, 100);
396 assert!(info.has_next);
397 assert!(info.has_prev);
398 }
399}