leptos_query_rs/mutation/
mod.rs1use leptos::prelude::*;
6use leptos::task::spawn_local;
7use std::future::Future;
8use serde::{Serialize, de::DeserializeOwned};
9
10use crate::client::QueryClient;
11use crate::retry::RetryConfig;
12use crate::types::QueryKeyPattern;
13
14#[derive(Clone)]
16pub struct MutationOptions {
17 pub enabled: bool,
19 pub retry: RetryConfig,
21 pub invalidate_queries: Option<Vec<QueryKeyPattern>>,
23}
24
25impl Default for MutationOptions {
26 fn default() -> Self {
27 Self {
28 enabled: true,
29 retry: RetryConfig::default(),
30 invalidate_queries: None,
31 }
32 }
33}
34
35impl MutationOptions {
36 pub fn with_retry(mut self, retry: RetryConfig) -> Self {
38 self.retry = retry;
39 self
40 }
41
42 pub fn invalidate_queries(mut self, patterns: Vec<QueryKeyPattern>) -> Self {
44 self.invalidate_queries = Some(patterns);
45 self
46 }
47
48 pub fn disabled(mut self) -> Self {
50 self.enabled = false;
51 self
52 }
53}
54
55#[derive(Clone)]
57pub struct MutationResult<TData: 'static + Send + Sync, TError: 'static + Send + Sync, TVariables: 'static> {
58 pub data: Signal<Option<TData>>,
60 pub error: Signal<Option<TError>>,
62 pub is_loading: Signal<bool>,
64 pub is_success: Signal<bool>,
66 pub is_error: Signal<bool>,
68
69 pub mutate: Callback<TVariables>,
72}
73
74pub fn use_mutation<TData, TError, TVariables, F, Fut>(
76 mutation_fn: F,
77 options: MutationOptions,
78) -> MutationResult<TData, TError, TVariables>
79where
80 TData: Clone + Send + Sync + Serialize + DeserializeOwned + 'static,
81 TError: Clone + Send + Sync + 'static,
82 TVariables: Clone + Send + Sync + 'static,
83 F: Fn(TVariables) -> Fut + Send + Sync + 'static + Clone,
84 Fut: Future<Output = Result<TData, TError>> + 'static,
85{
86 let (data, set_data) = signal(None::<TData>);
88 let (error, set_error) = signal(None::<TError>);
89 let (is_loading, set_loading) = signal(false);
90
91 let client = use_context::<QueryClient>().expect("QueryClient not found in context");
93
94 let execute_mutation = {
96 let client = client.clone();
97 let mutation_fn = mutation_fn.clone();
98 let options = options.clone();
99
100 move |vars: TVariables| {
101 let _client = client.clone();
102 let mutation_fn = mutation_fn.clone();
103 let options = options.clone();
104
105 spawn_local(async move {
106 set_loading.set(true);
107 set_error.set(None);
108
109 let result = mutation_fn(vars.clone()).await;
111
112 match result {
113 Ok(result_data) => {
114 set_data.set(Some(result_data));
115
116 if let Some(patterns) = &options.invalidate_queries {
118 for _pattern in patterns {
119 }
122 }
123 }
124 Err(err) => {
125 set_error.set(Some(err));
126 }
127 }
128
129 set_loading.set(false);
130 });
131 }
132 };
133
134 let is_success = Memo::new(move |_| data.get().is_some() && error.get().is_none());
136 let is_error = Memo::new(move |_| error.get().is_some());
137
138 MutationResult {
140 data: data.into(),
141 error: error.into(),
142 is_loading: is_loading.into(),
143 is_success: is_success.into(),
144 is_error: is_error.into(),
145 mutate: Callback::new(execute_mutation),
146 }
147}
148
149pub fn use_optimistic_mutation<TData, TError, TVariables, F, Fut>(
151 mutation_fn: F,
152 options: MutationOptions,
153 _on_success: impl Fn(&TData) + Send + Sync + 'static,
154 _on_error: impl Fn(&TError) + Send + Sync + 'static,
155) -> MutationResult<TData, TError, TVariables>
156where
157 TData: Clone + Send + Sync + Serialize + DeserializeOwned + 'static,
158 TError: Clone + Send + Sync + 'static,
159 TVariables: Clone + Send + Sync + 'static,
160 F: Fn(TVariables) -> Fut + Send + Sync + 'static + Clone,
161 Fut: Future<Output = Result<TData, TError>> + 'static,
162{
163 use_mutation(mutation_fn, options)
166}
167
168#[cfg(test)]
169mod tests {
170 use super::*;
171 use std::time::Duration;
172 use crate::types::QueryKey;
173
174 #[test]
175 fn test_mutation_options_builder() {
176 let options = MutationOptions::default()
177 .with_retry(RetryConfig::new(5, Duration::from_secs(2)))
178 .invalidate_queries(vec![QueryKeyPattern::Exact(QueryKey::from("users"))]);
179
180 assert_eq!(options.retry.max_retries, 5);
181 assert!(options.invalidate_queries.is_some());
182 }
183}