diff --git a/yew/src/html/mod.rs b/yew/src/html/mod.rs index 7b7f4958811..2aea064553d 100644 --- a/yew/src/html/mod.rs +++ b/yew/src/html/mod.rs @@ -457,6 +457,19 @@ impl NodeRef { this.node = None; this.link = Some(node_ref); } + + /// Reuse an existing `NodeRef` + pub(crate) fn reuse(&self, node_ref: Self) { + // Avoid circular references + if self == &node_ref { + return; + } + + let mut this = self.0.borrow_mut(); + let existing = node_ref.0.borrow(); + this.node = existing.node.clone(); + this.link = existing.link.clone(); + } } /// Trait for building properties for a component diff --git a/yew/src/html/scope.rs b/yew/src/html/scope.rs index 01fd6dfc326..6c0942864d8 100644 --- a/yew/src/html/scope.rs +++ b/yew/src/html/scope.rs @@ -23,8 +23,8 @@ pub(crate) enum ComponentUpdate { Message(COMP::Message), /// Wraps batch of messages for a component. MessageBatch(Vec), - /// Wraps properties and next sibling for a component. - Properties(COMP::Properties, NodeRef), + /// Wraps properties, node ref, and next sibling for a component. + Properties(COMP::Properties, NodeRef, NodeRef), } /// Untyped scope used for accessing parent scope @@ -445,7 +445,9 @@ where ComponentUpdate::MessageBatch(messages) => messages .into_iter() .fold(false, |acc, msg| state.component.update(msg) || acc), - ComponentUpdate::Properties(props, next_sibling) => { + ComponentUpdate::Properties(props, node_ref, next_sibling) => { + // When components are updated, a new node ref could have been passed in + state.node_ref = node_ref; // When components are updated, their siblings were likely also updated state.next_sibling = next_sibling; state.component.change(props) diff --git a/yew/src/virtual_dom/vcomp.rs b/yew/src/virtual_dom/vcomp.rs index fdbac1ac2f4..8c51de3f21e 100644 --- a/yew/src/virtual_dom/vcomp.rs +++ b/yew/src/virtual_dom/vcomp.rs @@ -122,7 +122,7 @@ trait Mountable { parent: Element, next_sibling: NodeRef, ) -> Box; - fn reuse(self: Box, scope: &dyn Scoped, next_sibling: NodeRef); + fn reuse(self: Box, node_ref: NodeRef, scope: &dyn Scoped, next_sibling: NodeRef); } struct PropsWrapper { @@ -162,9 +162,13 @@ impl Mountable for PropsWrapper { Box::new(scope) } - fn reuse(self: Box, scope: &dyn Scoped, next_sibling: NodeRef) { + fn reuse(self: Box, node_ref: NodeRef, scope: &dyn Scoped, next_sibling: NodeRef) { let scope: Scope = scope.to_any().downcast(); - scope.update(ComponentUpdate::Properties(self.props, next_sibling)); + scope.update(ComponentUpdate::Properties( + self.props, + node_ref, + next_sibling, + )); } } @@ -186,9 +190,9 @@ impl VDiff for VComp { if let VNode::VComp(ref mut vcomp) = &mut ancestor { // If the ancestor is the same type, reuse it and update its properties if self.type_id == vcomp.type_id && self.key == vcomp.key { - self.node_ref.link(vcomp.node_ref.clone()); + self.node_ref.reuse(vcomp.node_ref.clone()); let scope = vcomp.scope.take().expect("VComp is not mounted"); - mountable.reuse(scope.borrow(), next_sibling); + mountable.reuse(self.node_ref.clone(), scope.borrow(), next_sibling); self.scope = Some(scope); return vcomp.node_ref.clone(); } @@ -317,7 +321,7 @@ mod tests { } fn change(&mut self, _: Self::Properties) -> ShouldRender { - unimplemented!(); + true } fn view(&self) -> Html { @@ -325,6 +329,27 @@ mod tests { } } + #[test] + fn update_loop() { + let document = crate::utils::document(); + let parent_scope: AnyScope = crate::html::Scope::::new(None).into(); + let parent_element = document.create_element("div").unwrap(); + + let mut ancestor = html! { }; + ancestor.apply(&parent_scope, &parent_element, NodeRef::default(), None); + + for _ in 0..10000 { + let mut node = html! { }; + node.apply( + &parent_scope, + &parent_element, + NodeRef::default(), + Some(ancestor), + ); + ancestor = node; + } + } + #[test] fn set_properties_to_component() { html! {