Browse Source

Fix issue with mutability in component macro

Jonathan Kelley 1 year ago
parent
commit
98bd78de43

+ 5 - 0
examples/signals.rs

@@ -17,7 +17,12 @@ fn app() -> Element {
             if running() {
                 count += 1;
             }
+
+            let val = running.read();
+
             tokio::time::sleep(Duration::from_millis(400)).await;
+
+            println!("Running: {}", *val);
         }
     });
 

+ 4 - 8
examples/todomvc.rs

@@ -80,8 +80,7 @@ pub fn app() -> Element {
 }
 
 #[component]
-pub fn TodoHeader(todos: Signal<HashMap<u32, TodoItem>>) -> Element {
-    let mut todos = todos;
+pub fn TodoHeader(mut todos: Signal<HashMap<u32, TodoItem>>) -> Element {
     let mut draft = use_signal(|| "".to_string());
     let mut todo_id = use_signal(|| 0);
 
@@ -115,8 +114,7 @@ pub fn TodoHeader(todos: Signal<HashMap<u32, TodoItem>>) -> Element {
 }
 
 #[component]
-pub fn TodoEntry(todos: Signal<HashMap<u32, TodoItem>>, id: u32) -> Element {
-    let mut todos = todos;
+pub fn TodoEntry(mut todos: Signal<HashMap<u32, TodoItem>>, id: u32) -> Element {
     let mut is_editing = use_signal(|| false);
     let checked = use_selector(move || todos.read().get(&id).unwrap().checked);
     let contents = use_selector(move || todos.read().get(&id).unwrap().contents.clone());
@@ -164,12 +162,10 @@ pub fn TodoEntry(todos: Signal<HashMap<u32, TodoItem>>, id: u32) -> Element {
 
 #[component]
 pub fn ListFooter(
-    todos: Signal<HashMap<u32, TodoItem>>,
+    mut todos: Signal<HashMap<u32, TodoItem>>,
     active_todo_count: ReadOnlySignal<usize>,
-    filter: Signal<FilterState>,
+    mut filter: Signal<FilterState>,
 ) -> Element {
-    let mut todos = todos;
-    let mut filter = filter;
     let show_clear_completed = use_selector(move || todos.read().values().any(|todo| todo.checked));
 
     rsx! {

+ 26 - 4
packages/core-macro/src/component_body_deserializers/inline_props.rs

@@ -46,12 +46,20 @@ fn get_props_struct(component_body: &ComponentBody) -> ItemStruct {
         ..
     } = sig;
 
-    // Skip first arg since that's the context
     let struct_fields = inputs.iter().map(move |f| {
         match f {
             FnArg::Receiver(_) => unreachable!(), // Unreachable because of ComponentBody parsing
             FnArg::Typed(pt) => {
-                let arg_pat = &pt.pat; // Pattern (identifier)
+                let arg_pat = match pt.pat.as_ref() {
+                    // rip off mutability
+                    Pat::Ident(f) => {
+                        let mut f = f.clone();
+                        f.mutability = None;
+                        quote! { #f }
+                    }
+                    a => quote! { #a },
+                };
+
                 let arg_colon = &pt.colon_token;
                 let arg_ty = &pt.ty; // Type
                 let arg_attrs = &pt.attrs; // Attributes
@@ -247,7 +255,21 @@ fn get_function(component_body: &ComponentBody) -> ItemFn {
     // Skip first arg since that's the context
     let struct_field_names = inputs.iter().filter_map(|f| match f {
         FnArg::Receiver(_) => unreachable!(), // ComponentBody prohibits receiver parameters.
-        FnArg::Typed(pt) => Some(&pt.pat),
+        FnArg::Typed(pt) => {
+            let pat = &pt.pat;
+
+            let mut pat = pat.clone();
+
+            // rip off mutability, but still write it out eventually
+            match pat.as_mut() {
+                Pat::Ident(ref mut pat_ident) => {
+                    pat_ident.mutability = None;
+                }
+                _ => {}
+            }
+
+            Some(quote!(mut  #pat))
+        }
     });
 
     let first_lifetime = if let Some(GenericParam::Lifetime(lt)) = generics.params.first() {
@@ -293,7 +315,7 @@ fn get_function(component_body: &ComponentBody) -> ItemFn {
     parse_quote! {
         #(#fn_attrs)*
         #(#props_docs)*
-        #asyncness #vis fn #fn_ident #fn_generics (__props: #struct_ident #generics_no_bounds) #fn_output
+        #asyncness #vis fn #fn_ident #fn_generics (mut __props: #struct_ident #generics_no_bounds) #fn_output
         #where_clause
         {
             let #struct_ident { #(#struct_field_names),* } = __props;

+ 2 - 2
packages/router/src/components/history_buttons.rs

@@ -57,7 +57,7 @@ pub struct HistoryButtonProps {
 #[component]
 pub fn GoBackButton(
     /// The props...
-    props: HistoryButtonProps,
+    mut props: HistoryButtonProps,
 ) -> Element {
     let HistoryButtonProps { children } = props;
 
@@ -127,7 +127,7 @@ pub fn GoBackButton(
 #[component]
 pub fn GoForwardButton(
     /// Props...
-    props: HistoryButtonProps,
+    mut props: HistoryButtonProps,
 ) -> Element {
     let HistoryButtonProps { children } = props;
 

+ 10 - 8
packages/signals/src/signal.rs

@@ -245,7 +245,17 @@ impl<T: 'static> Signal<T> {
         GenerationalRef::map(inner, |v| &v.value)
     }
 
+    /// Get a mutable reference to the signal's value.
+    ///
+    /// If the signal has been dropped, this will panic.
+    #[track_caller]
+    pub fn write<'a>(&'a mut self) -> Write<'a, T> {
+        self.write_unchecked()
+    }
+
     /// Write to the value through an immutable reference.
+    ///
+    /// This is public since it's useful in many scenarios, but we generally recommend mutation through [`Self::write`] instead.
     pub fn write_unchecked(&self) -> Write<T> {
         let inner = self.inner.write();
         let borrow = GenerationalRefMut::map(inner, |v| &mut v.value);
@@ -255,14 +265,6 @@ impl<T: 'static> Signal<T> {
         }
     }
 
-    /// Get a mutable reference to the signal's value.
-    ///
-    /// If the signal has been dropped, this will panic.
-    #[track_caller]
-    pub fn write<'a>(&'a mut self) -> Write<'a, T> {
-        self.write_unchecked()
-    }
-
     fn update_subscribers(&self) {
         {
             let inner = self.inner.read();