Repository: DioxusLabs/dioxus (Rust)

Issue:
Props spreading causes an infinite loop
**Problem**

https://github.com/DioxusLabs/dioxus/blob/5b6f9d6b80ef06c83a55a5399d3ddcd7e29479df/packages/core/src/diff.rs#L144-L163

It appears that the iterator is not consumed when it enters this case.
I tried calling left_iter.next() right_iter.next() and the infinite loop was resolved.
Is this the right way to fix it?

**Environment:**
 - Dioxus version: master
 - Rust version: 1.75.0]
 - OS info: Windows
 - App platform: web

**Questionnaire**
<!-- If you feel up to the challenge, please check one of the boxes below: -->
- [ ] I'm interested in fixing this myself but don't know where to start
- [x] I would like to fix and I have a solution, but I don't know how to test it.
- [ ] I don't have time to fix this right now, but maybe later


Discussion:
Component definitions are here:
https://github.com/atty303/bb2-building/blob/63d99f345f604020143ede9b9cda4f5f93106c61/src/components/tabs.rs

Here is what we use:
https://github.com/atty303/bb2-building/blob/63d99f345f604020143ede9b9cda4f5f93106c61/src/components/skill_view.rs#L71-L128
Yes, the item should be consumed in each of those branches.

You can test the fix by trying to diff two spread attributes with a different number of attributes (both small -> large and large -> small)



Patch:
diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs
index a116aee4d1..b5f262b0cb 100644
--- a/packages/core/src/diff.rs
+++ b/packages/core/src/diff.rs
@@ -145,19 +145,27 @@ impl<'b> VirtualDom {
                                 (Some(left), Some(right)) => {
                                     // check which name is greater
                                     match left.name.cmp(right.name) {
-                                        std::cmp::Ordering::Less => self.remove_attribute(
-                                            left.name,
-                                            left.namespace,
-                                            mounted_id,
-                                        ),
-                                        std::cmp::Ordering::Greater => self.write_attribute(
-                                            right_template,
-                                            right,
-                                            idx,
-                                            mounted_id,
-                                        ),
+                                        std::cmp::Ordering::Less => {
+                                            self.remove_attribute(
+                                                left.name,
+                                                left.namespace,
+                                                mounted_id,
+                                            );
+                                            left_iter.next();
+                                        }
+                                        std::cmp::Ordering::Greater => {
+                                            self.write_attribute(
+                                                right_template,
+                                                right,
+                                                idx,
+                                                mounted_id,
+                                            );
+                                            right_iter.next();
+                                        }
                                         std::cmp::Ordering::Equal => {
-                                            self.diff_attribute(left, right, mounted_id)
+                                            self.diff_attribute(left, right, mounted_id);
+                                            left_iter.next();
+                                            right_iter.next();
                                         }
                                     }
                                 }


Test patch:
diff --git a/packages/core/tests/diff_element.rs b/packages/core/tests/diff_element.rs
index a0b9bfa844..7169de6098 100644
--- a/packages/core/tests/diff_element.rs
+++ b/packages/core/tests/diff_element.rs
@@ -1,6 +1,6 @@
 use dioxus::core::Mutation::*;
 use dioxus::prelude::*;
-use dioxus_core::ElementId;
+use dioxus_core::{AttributeValue, ElementId};
 
 #[test]
 fn text_diff() {
@@ -82,3 +82,122 @@ fn element_swap() {
         ]
     );
 }
+
+#[test]
+fn attribute_diff() {
+    fn app(cx: Scope) -> Element {
+        let gen = cx.generation();
+
+        // attributes have to be sorted by name
+        let attrs = match gen % 5 {
+            0 => cx.bump().alloc([Attribute::new(
+                "a",
+                AttributeValue::Text("hello"),
+                None,
+                false,
+            )]) as &[Attribute],
+            1 => cx.bump().alloc([
+                Attribute::new("a", AttributeValue::Text("hello"), None, false),
+                Attribute::new("b", AttributeValue::Text("hello"), None, false),
+                Attribute::new("c", AttributeValue::Text("hello"), None, false),
+            ]) as &[Attribute],
+            2 => cx.bump().alloc([
+                Attribute::new("c", AttributeValue::Text("hello"), None, false),
+                Attribute::new("d", AttributeValue::Text("hello"), None, false),
+                Attribute::new("e", AttributeValue::Text("hello"), None, false),
+            ]) as &[Attribute],
+            3 => cx.bump().alloc([Attribute::new(
+                "d",
+                AttributeValue::Text("world"),
+                None,
+                false,
+            )]) as &[Attribute],
+            _ => unreachable!(),
+        };
+
+        cx.render(rsx!(
+            div {
+                ..*attrs,
+                "hello"
+            }
+        ))
+    }
+
+    let mut vdom = VirtualDom::new(app);
+    _ = vdom.rebuild();
+
+    vdom.mark_dirty(ScopeId::ROOT);
+    assert_eq!(
+        vdom.render_immediate().santize().edits,
+        [
+            SetAttribute {
+                name: "b",
+                value: (&AttributeValue::Text("hello",)).into(),
+                id: ElementId(1,),
+                ns: None,
+            },
+            SetAttribute {
+                name: "c",
+                value: (&AttributeValue::Text("hello",)).into(),
+                id: ElementId(1,),
+                ns: None,
+            },
+        ]
+    );
+
+    vdom.mark_dirty(ScopeId::ROOT);
+    assert_eq!(
+        vdom.render_immediate().santize().edits,
+        [
+            SetAttribute {
+                name: "a",
+                value: (&AttributeValue::None).into(),
+                id: ElementId(1,),
+                ns: None,
+            },
+            SetAttribute {
+                name: "b",
+                value: (&AttributeValue::None).into(),
+                id: ElementId(1,),
+                ns: None,
+            },
+            SetAttribute {
+                name: "d",
+                value: (&AttributeValue::Text("hello",)).into(),
+                id: ElementId(1,),
+                ns: None,
+            },
+            SetAttribute {
+                name: "e",
+                value: (&AttributeValue::Text("hello",)).into(),
+                id: ElementId(1,),
+                ns: None,
+            },
+        ]
+    );
+
+    vdom.mark_dirty(ScopeId::ROOT);
+    assert_eq!(
+        vdom.render_immediate().santize().edits,
+        [
+            SetAttribute {
+                name: "c",
+                value: (&AttributeValue::None).into(),
+                id: ElementId(1,),
+                ns: None,
+            },
+            SetAttribute {
+                name: "d",
+                value: (&AttributeValue::Text("world",)).into(),
+                id: ElementId(1,),
+                ns: None,
+            },
+            SetAttribute {
+                name: "e",
+                value: (&AttributeValue::None).into(),
+                id: ElementId(1,),
+                ns: None,
+            },
+        ]
+    );
+}