sycamore_query/
mutation.rs

1use std::{future::Future, rc::Rc};
2
3use sycamore::{
4    futures::spawn_local_scoped,
5    reactive::{create_ref, create_signal, use_context, ReadSignal, Scope, Signal},
6};
7
8use crate::{client::QueryOptions, QueryClient, QueryData, Status};
9
10/// The struct representing a mutation
11///
12/// # Example
13///
14/// ```
15/// # use sycamore::prelude::*;
16/// # use sycamore_query::{*, mutation::{Mutation, use_mutation}};
17/// # #[component]
18/// # pub fn App<G: Html>(cx: Scope) -> View<G> {
19/// #   provide_context(cx, QueryClient::new(ClientOptions::default()));
20/// let Mutation { data, status, mutate } = use_mutation(
21///     cx,
22///     |name: String| async { Result::<_, ()>::Ok(name) },
23///     |client, data| client.set_query_data("name", data)
24/// );
25///
26/// mutate("World".to_string());
27/// # view! { cx, }
28/// # }
29/// ```
30pub struct Mutation<'a, T, E, Args> {
31    /// The data returned by the mutation, if any
32    pub data: &'a ReadSignal<QueryData<Rc<T>, Rc<E>>>,
33    /// The status of the mutation
34    pub status: &'a ReadSignal<Status>,
35    /// The mutation function. This takes in the arguments for the mutator
36    /// function and tries to execute the mutation.
37    pub mutate: &'a dyn Fn(Args),
38}
39
40impl QueryClient {
41    pub(crate) fn run_mutation<'a, T, E, Mutate, R, Args, Success>(
42        &self,
43        cx: Scope<'a>,
44        data: &'a Signal<QueryData<Rc<T>, Rc<E>>>,
45        status: &'a Signal<Status>,
46        mutator: &'a Mutate,
47        args: Args,
48        on_success: &'a Success,
49    ) where
50        Mutate: Fn(Args) -> R,
51        R: Future<Output = Result<T, E>>,
52        Success: Fn(Rc<QueryClient>, Rc<T>),
53        Args: 'a,
54    {
55        status.set(Status::Fetching);
56        spawn_local_scoped(cx, async move {
57            let res = mutator(args).await;
58            data.set(res.map_or_else(
59                |err| QueryData::Err(Rc::new(err)),
60                |data| QueryData::Ok(Rc::new(data)),
61            ));
62            if let QueryData::Ok(ok) = data.get().as_ref() {
63                let client = use_context::<Rc<QueryClient>>(cx);
64                on_success(client.clone(), ok.clone());
65            }
66            status.set(Status::Success);
67        });
68    }
69}
70
71/// Use a mutation that updates data on the server.
72///
73/// # Parameters
74///
75/// * `cx` - The scope for the component the mutation is in.
76/// * `mutator` - The function that actually executes the mutation on the server.
77/// This can take in any type of arguments.
78/// * `on_success` - Function to execute when the mutation is successful. Used to
79/// invalidate queries or update queries with data returned by the mutation.
80///
81/// # Returns
82///
83/// A [`Mutation`] struct.
84///
85/// # Example
86///
87/// ```
88/// # use sycamore::prelude::*;
89/// # use sycamore_query::{*, mutation::{Mutation, use_mutation}};
90/// # #[component]
91/// # pub fn App<G: Html>(cx: Scope) -> View<G> {
92/// #   provide_context(cx, QueryClient::new(ClientOptions::default()));
93/// let Mutation { data, status, mutate } = use_mutation(
94///     cx,
95///     |name: String| async { Result::<_, ()>::Ok(name) },
96///     |client, data| client.set_query_data("name", data)
97/// );
98/// # view! { cx, }
99/// # }
100pub fn use_mutation<'a, Args, T, E, F, R, Success>(
101    cx: Scope<'a>,
102    mutator: F,
103    on_success: Success,
104) -> Mutation<'a, T, E, Args>
105where
106    F: Fn(Args) -> R + 'a,
107    R: Future<Output = Result<T, E>>,
108    Success: Fn(Rc<QueryClient>, Rc<T>) + 'a,
109{
110    use_mutation_with_options(cx, mutator, on_success, QueryOptions::default())
111}
112
113/// Use a mutation with additional query options. For more information, see
114/// [`use_mutation`] and [`QueryOptions`]
115pub fn use_mutation_with_options<'a, Args, T, E, F, R, Success>(
116    cx: Scope<'a>,
117    mutator: F,
118    on_success: Success,
119    _options: QueryOptions,
120) -> Mutation<'a, T, E, Args>
121where
122    F: Fn(Args) -> R + 'a,
123    R: Future<Output = Result<T, E>>,
124    Success: Fn(Rc<QueryClient>, Rc<T>) + 'a,
125{
126    let client = use_context::<Rc<QueryClient>>(cx).clone();
127    let data: &Signal<QueryData<Rc<T>, Rc<E>>> = create_signal(cx, QueryData::Loading);
128    let status = create_signal(cx, Status::Fetching);
129    let mutator = create_ref(cx, mutator);
130    let on_success = create_ref(cx, on_success);
131
132    let mutate = create_ref(cx, move |args: Args| {
133        client.run_mutation(cx, data, status, mutator, args, on_success)
134    });
135
136    Mutation {
137        data,
138        mutate,
139        status,
140    }
141}