From 255a4883514fe85fe17cff6fcadccc3adef21d90 Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Fri, 6 May 2022 20:11:29 +0900 Subject: [PATCH 1/7] Delay props. --- packages/yew/src/html/component/lifecycle.rs | 75 ++++++++++++-------- packages/yew/src/html/component/scope.rs | 2 +- packages/yew/tests/hydration.rs | 67 ++++++++++++++++- 3 files changed, 112 insertions(+), 32 deletions(-) diff --git a/packages/yew/src/html/component/lifecycle.rs b/packages/yew/src/html/component/lifecycle.rs index 517c123d341..8f5b8098808 100644 --- a/packages/yew/src/html/component/lifecycle.rs +++ b/packages/yew/src/html/component/lifecycle.rs @@ -221,6 +221,8 @@ pub(crate) struct ComponentState { #[cfg(feature = "csr")] has_rendered: bool, + #[cfg(feature = "csr")] + pending_props: Option>, suspension: Option, @@ -263,6 +265,8 @@ impl ComponentState { #[cfg(feature = "csr")] has_rendered: false, + #[cfg(feature = "csr")] + pending_props: None, comp_id, } @@ -305,7 +309,7 @@ impl Runnable for CreateRunner { pub(crate) struct PropsUpdateRunner { pub props: Rc, pub state: Shared>, - pub next_sibling: NodeRef, + pub next_sibling: Option, } #[cfg(feature = "csr")] @@ -318,37 +322,40 @@ impl Runnable for PropsUpdateRunner { } = *self; if let Some(state) = shared_state.borrow_mut().as_mut() { - let schedule_render = match state.render_state { - #[cfg(feature = "csr")] - ComponentRenderState::Render { - next_sibling: ref mut current_next_sibling, - .. - } => { - // When components are updated, their siblings were likely also updated - *current_next_sibling = next_sibling; - // Only trigger changed if props were changed - state.inner.props_changed(props) + if let Some(next_sibling) = next_sibling { + match state.render_state { + #[cfg(feature = "csr")] + ComponentRenderState::Render { + next_sibling: ref mut current_next_sibling, + .. + } => { + // When components are updated, their siblings were likely also updated + *current_next_sibling = next_sibling; + } + + #[cfg(feature = "hydration")] + ComponentRenderState::Hydration { + next_sibling: ref mut current_next_sibling, + .. + } => { + // When components are updated, their siblings were likely also updated + *current_next_sibling = next_sibling; + } + + #[cfg(feature = "ssr")] + ComponentRenderState::Ssr { .. } => { + #[cfg(debug_assertions)] + panic!("properties do not change during SSR"); + } } + } - #[cfg(feature = "hydration")] - ComponentRenderState::Hydration { - next_sibling: ref mut current_next_sibling, - .. - } => { - // When components are updated, their siblings were likely also updated - *current_next_sibling = next_sibling; - // Only trigger changed if props were changed - state.inner.props_changed(props) - } - - #[cfg(feature = "ssr")] - ComponentRenderState::Ssr { .. } => { - #[cfg(debug_assertions)] - panic!("properties do not change during SSR"); - - #[cfg(not(debug_assertions))] - false - } + // Only trigger changed if props were changed and the component has rendered. + let schedule_render = if state.has_rendered { + state.inner.props_changed(props) + } else { + state.pending_props = Some(props); + true }; #[cfg(debug_assertions)] @@ -621,6 +628,14 @@ mod feat_csr { if state.suspension.is_none() { state.inner.rendered(self.first_render); } + + if let Some(m) = state.pending_props.take() { + scheduler::push_component_props_update(Box::new(PropsUpdateRunner { + props: m, + state: self.state.clone(), + next_sibling: None, + })); + } } } } diff --git a/packages/yew/src/html/component/scope.rs b/packages/yew/src/html/component/scope.rs index dc579b9c219..7851cb504c2 100644 --- a/packages/yew/src/html/component/scope.rs +++ b/packages/yew/src/html/component/scope.rs @@ -423,7 +423,7 @@ mod feat_csr { ) { scheduler::push_component_props_update(Box::new(PropsUpdateRunner { state, - next_sibling, + next_sibling: Some(next_sibling), props, })); // Not guaranteed to already have the scheduler started diff --git a/packages/yew/tests/hydration.rs b/packages/yew/tests/hydration.rs index b0d249015fe..38e0366b8c7 100644 --- a/packages/yew/tests/hydration.rs +++ b/packages/yew/tests/hydration.rs @@ -1,6 +1,7 @@ #![cfg(feature = "hydration")] #![cfg(target_arch = "wasm32")] +use std::ops::Range; use std::rc::Rc; use std::time::Duration; @@ -834,7 +835,7 @@ async fn hydration_order_issue_nested_suspense() { pub fn app() -> Html { let elems = (0..10).map(|number: u32| { html! { - + } }); @@ -913,3 +914,67 @@ async fn hydration_order_issue_nested_suspense() { r#"
0
1
2
3
4
5
6
7
8
9
"# ); } + +#[wasm_bindgen_test] +async fn hydration_props_blocked_until_hydrated() { + #[function_component(App)] + pub fn app() -> Html { + let range = use_state(|| 0u32..2); + { + let range = range.clone(); + use_effect_with_deps( + move |_| { + range.set(0..3); + || () + }, + (), + ); + } + + html! { + + + + } + } + + #[derive(Properties, PartialEq)] + struct ToSuspendProps { + range: Range, + } + + #[function_component(ToSuspend)] + fn to_suspend(ToSuspendProps { range }: &ToSuspendProps) -> HtmlResult { + use_suspend(100)?; + Ok(html! { + { for range.clone().map(|i| + html!{
{i}
} + )} + }) + } + + #[hook] + pub fn use_suspend(_wait: u64) -> SuspensionResult<()> { + yew::suspense::use_future(|| async move { + #[cfg(target_arch = "wasm32")] + gloo::timers::future::sleep(Duration::from_millis(_wait)).await; + })?; + + Ok(()) + } + + let s = ServerRenderer::::new().render().await; + + gloo::utils::document() + .query_selector("#output") + .unwrap() + .unwrap() + .set_inner_html(&s); + + Renderer::::with_root(gloo_utils::document().get_element_by_id("output").unwrap()) + .hydrate(); + sleep(Duration::from_millis(150)).await; + + let result = obtain_result_by_id("output"); + assert_eq!(result.as_str(), r#"
0
1
2
"#); +} From c586efa392c73bbfbb8f5c283a77e66be4822bef Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Fri, 6 May 2022 21:32:34 +0900 Subject: [PATCH 2/7] Fix next sibling change not synced. --- packages/yew/src/html/component/lifecycle.rs | 76 ++++++++++++-------- 1 file changed, 46 insertions(+), 30 deletions(-) diff --git a/packages/yew/src/html/component/lifecycle.rs b/packages/yew/src/html/component/lifecycle.rs index 8f5b8098808..f7be83300ea 100644 --- a/packages/yew/src/html/component/lifecycle.rs +++ b/packages/yew/src/html/component/lifecycle.rs @@ -198,7 +198,7 @@ where }; if self.context.props != props { - self.context.props = Rc::clone(&props); + self.context.props = props; self.component.changed(&self.context) } else { false @@ -322,46 +322,62 @@ impl Runnable for PropsUpdateRunner { } = *self; if let Some(state) = shared_state.borrow_mut().as_mut() { - if let Some(next_sibling) = next_sibling { - match state.render_state { - #[cfg(feature = "csr")] - ComponentRenderState::Render { - next_sibling: ref mut current_next_sibling, - .. - } => { - // When components are updated, their siblings were likely also updated - *current_next_sibling = next_sibling; - } - - #[cfg(feature = "hydration")] - ComponentRenderState::Hydration { - next_sibling: ref mut current_next_sibling, - .. - } => { - // When components are updated, their siblings were likely also updated - *current_next_sibling = next_sibling; - } - - #[cfg(feature = "ssr")] - ComponentRenderState::Ssr { .. } => { - #[cfg(debug_assertions)] - panic!("properties do not change during SSR"); + let next_sibling_changed = match next_sibling { + Some(next_sibling) => { + match state.render_state { + #[cfg(feature = "csr")] + ComponentRenderState::Render { + next_sibling: ref mut current_next_sibling, + .. + } => { + let changed = current_next_sibling.get() != next_sibling.get(); + // When components are updated, their siblings were likely also updated + *current_next_sibling = next_sibling; + + changed + } + + #[cfg(feature = "hydration")] + ComponentRenderState::Hydration { + next_sibling: ref mut current_next_sibling, + .. + } => { + let changed = current_next_sibling.get() != next_sibling.get(); + + // When components are updated, their siblings were likely also updated + *current_next_sibling = next_sibling; + + changed + } + + #[cfg(feature = "ssr")] + ComponentRenderState::Ssr { .. } => { + #[cfg(debug_assertions)] + panic!("properties do not change during SSR"); + + #[cfg(not(debug_assertions))] + false + } } } - } + None => false, + }; - // Only trigger changed if props were changed and the component has rendered. + // Only trigger changed if props were changed / next sibling has changed. let schedule_render = if state.has_rendered { - state.inner.props_changed(props) + state.inner.props_changed(props) || next_sibling_changed } else { state.pending_props = Some(props); - true + next_sibling_changed }; #[cfg(debug_assertions)] super::log_event( state.comp_id, - format!("props_update(schedule_render={})", schedule_render), + format!( + "props_update(has_rendered={} schedule_render={})", + state.has_rendered, schedule_render + ), ); if schedule_render { From fe37a131cee538855b89f4e7d3cee9f6665da864 Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Fri, 6 May 2022 22:32:17 +0900 Subject: [PATCH 3/7] Use shifting instead. --- packages/yew/src/html/component/lifecycle.rs | 73 +++++++++----------- 1 file changed, 33 insertions(+), 40 deletions(-) diff --git a/packages/yew/src/html/component/lifecycle.rs b/packages/yew/src/html/component/lifecycle.rs index f7be83300ea..8962fda55e3 100644 --- a/packages/yew/src/html/component/lifecycle.rs +++ b/packages/yew/src/html/component/lifecycle.rs @@ -322,53 +322,46 @@ impl Runnable for PropsUpdateRunner { } = *self; if let Some(state) = shared_state.borrow_mut().as_mut() { - let next_sibling_changed = match next_sibling { - Some(next_sibling) => { - match state.render_state { - #[cfg(feature = "csr")] - ComponentRenderState::Render { - next_sibling: ref mut current_next_sibling, - .. - } => { - let changed = current_next_sibling.get() != next_sibling.get(); - // When components are updated, their siblings were likely also updated - *current_next_sibling = next_sibling; - - changed - } - - #[cfg(feature = "hydration")] - ComponentRenderState::Hydration { - next_sibling: ref mut current_next_sibling, - .. - } => { - let changed = current_next_sibling.get() != next_sibling.get(); - - // When components are updated, their siblings were likely also updated - *current_next_sibling = next_sibling; - - changed - } - - #[cfg(feature = "ssr")] - ComponentRenderState::Ssr { .. } => { - #[cfg(debug_assertions)] - panic!("properties do not change during SSR"); - - #[cfg(not(debug_assertions))] - false - } + if let Some(next_sibling) = next_sibling { + match state.render_state { + #[cfg(feature = "csr")] + ComponentRenderState::Render { + next_sibling: ref mut current_next_sibling, + ref parent, + ref bundle, + .. + } => { + bundle.shift(parent, next_sibling.clone()); + // When components are updated, their siblings were likely also updated + *current_next_sibling = next_sibling; + } + + #[cfg(feature = "hydration")] + ComponentRenderState::Hydration { + next_sibling: ref mut current_next_sibling, + ref parent, + ref fragment, + .. + } => { + fragment.shift(parent, next_sibling.clone()); + // When components are updated, their siblings were likely also updated + *current_next_sibling = next_sibling; + } + + #[cfg(feature = "ssr")] + ComponentRenderState::Ssr { .. } => { + #[cfg(debug_assertions)] + panic!("properties do not change during SSR"); } } - None => false, - }; + } // Only trigger changed if props were changed / next sibling has changed. let schedule_render = if state.has_rendered { - state.inner.props_changed(props) || next_sibling_changed + state.inner.props_changed(props) } else { state.pending_props = Some(props); - next_sibling_changed + false }; #[cfg(debug_assertions)] From c388299b370b8eef289836ac5dfb3b55fdf8a4d2 Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Sat, 7 May 2022 01:57:04 +0900 Subject: [PATCH 4/7] Update docs, minor adjustments. --- packages/yew/src/html/component/lifecycle.rs | 16 +++++++++------- packages/yew/src/html/component/mod.rs | 9 +++++++++ 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/packages/yew/src/html/component/lifecycle.rs b/packages/yew/src/html/component/lifecycle.rs index 8962fda55e3..ab2116d5952 100644 --- a/packages/yew/src/html/component/lifecycle.rs +++ b/packages/yew/src/html/component/lifecycle.rs @@ -323,6 +323,9 @@ impl Runnable for PropsUpdateRunner { if let Some(state) = shared_state.borrow_mut().as_mut() { if let Some(next_sibling) = next_sibling { + // When components are updated, their siblings were likely also updated + // We also need to shift the bundle so next sibling will be synced to child + // components. match state.render_state { #[cfg(feature = "csr")] ComponentRenderState::Render { @@ -332,7 +335,6 @@ impl Runnable for PropsUpdateRunner { .. } => { bundle.shift(parent, next_sibling.clone()); - // When components are updated, their siblings were likely also updated *current_next_sibling = next_sibling; } @@ -344,7 +346,6 @@ impl Runnable for PropsUpdateRunner { .. } => { fragment.shift(parent, next_sibling.clone()); - // When components are updated, their siblings were likely also updated *current_next_sibling = next_sibling; } @@ -357,11 +358,12 @@ impl Runnable for PropsUpdateRunner { } // Only trigger changed if props were changed / next sibling has changed. - let schedule_render = if state.has_rendered { - state.inner.props_changed(props) - } else { - state.pending_props = Some(props); - false + let schedule_render = match state.has_rendered { + true => state.inner.props_changed(props), + false => { + state.pending_props = Some(props); + false + } }; #[cfg(debug_assertions)] diff --git a/packages/yew/src/html/component/mod.rs b/packages/yew/src/html/component/mod.rs index edd1809181d..0e3bc9e40af 100644 --- a/packages/yew/src/html/component/mod.rs +++ b/packages/yew/src/html/component/mod.rs @@ -97,6 +97,15 @@ impl Context { /// /// We provide a blanket implementation of this trait for every member that implements /// [`Component`]. +/// +/// # Warning +/// +/// This trait may be subject to heavy changes between versions and is not intended for direct +/// implementation. +/// +/// You should used the [`Component`] trait or the +/// [`#[function_component]`](crate::functional::function_component) macro to define your +/// components. pub trait BaseComponent: Sized + 'static { /// The Component's Message. type Message: 'static; From 8965ac172f3295e3444edc7c91e0ef2031a067d1 Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Sun, 8 May 2022 12:55:44 +0900 Subject: [PATCH 5/7] More predictable props update. --- packages/yew/src/html/component/lifecycle.rs | 24 +++++++++++------ packages/yew/src/html/component/scope.rs | 2 +- packages/yew/tests/hydration.rs | 27 ++++++++++---------- 3 files changed, 30 insertions(+), 23 deletions(-) diff --git a/packages/yew/src/html/component/lifecycle.rs b/packages/yew/src/html/component/lifecycle.rs index ab2116d5952..cc743c9a8a6 100644 --- a/packages/yew/src/html/component/lifecycle.rs +++ b/packages/yew/src/html/component/lifecycle.rs @@ -307,7 +307,7 @@ impl Runnable for CreateRunner { #[cfg(feature = "csr")] pub(crate) struct PropsUpdateRunner { - pub props: Rc, + pub props: Option>, pub state: Shared>, pub next_sibling: Option, } @@ -358,12 +358,20 @@ impl Runnable for PropsUpdateRunner { } // Only trigger changed if props were changed / next sibling has changed. - let schedule_render = match state.has_rendered { - true => state.inner.props_changed(props), - false => { - state.pending_props = Some(props); - false + let schedule_render = if let Some(props) = props.or_else(|| state.pending_props.take()) + { + match state.has_rendered { + true => { + state.pending_props = None; + state.inner.props_changed(props) + } + false => { + state.pending_props = Some(props); + false + } } + } else { + false }; #[cfg(debug_assertions)] @@ -640,9 +648,9 @@ mod feat_csr { state.inner.rendered(self.first_render); } - if let Some(m) = state.pending_props.take() { + if state.pending_props.is_some() { scheduler::push_component_props_update(Box::new(PropsUpdateRunner { - props: m, + props: None, state: self.state.clone(), next_sibling: None, })); diff --git a/packages/yew/src/html/component/scope.rs b/packages/yew/src/html/component/scope.rs index 7851cb504c2..179052ca61b 100644 --- a/packages/yew/src/html/component/scope.rs +++ b/packages/yew/src/html/component/scope.rs @@ -424,7 +424,7 @@ mod feat_csr { scheduler::push_component_props_update(Box::new(PropsUpdateRunner { state, next_sibling: Some(next_sibling), - props, + props: Some(props), })); // Not guaranteed to already have the scheduler started scheduler::start(); diff --git a/packages/yew/tests/hydration.rs b/packages/yew/tests/hydration.rs index 38e0366b8c7..cf03e0a744c 100644 --- a/packages/yew/tests/hydration.rs +++ b/packages/yew/tests/hydration.rs @@ -769,7 +769,7 @@ async fn hydration_suspense_no_flickering() { #[hook] pub fn use_suspend() -> SuspensionResult<()> { use_future(|| async { - gloo::timers::future::sleep(std::time::Duration::from_millis(50)).await; + gloo::timers::future::sleep(std::time::Duration::from_millis(100)).await; })?; Ok(()) } @@ -796,21 +796,21 @@ async fn hydration_suspense_no_flickering() { // outer still suspended. r#"
0
1
2
3
4
5
6
7
8
9
"# ); - sleep(Duration::from_millis(26)).await; + sleep(Duration::from_millis(52)).await; let result = obtain_result_by_id("output"); assert_eq!( result.as_str(), r#"
0
1
2
3
4
5
6
7
8
9
"# ); - sleep(Duration::from_millis(26)).await; + sleep(Duration::from_millis(52)).await; let result = obtain_result_by_id("output"); assert_eq!( result.as_str(), r#"
0
1
2
3
4
5
6
7
8
9
"# ); - sleep(Duration::from_millis(26)).await; + sleep(Duration::from_millis(52)).await; let result = obtain_result_by_id("output"); assert_eq!( @@ -819,7 +819,7 @@ async fn hydration_suspense_no_flickering() { r#"
0
1
2
3
4
5
6
7
8
9
"# ); - sleep(Duration::from_millis(26)).await; + sleep(Duration::from_millis(52)).await; let result = obtain_result_by_id("output"); assert_eq!( @@ -945,7 +945,7 @@ async fn hydration_props_blocked_until_hydrated() { #[function_component(ToSuspend)] fn to_suspend(ToSuspendProps { range }: &ToSuspendProps) -> HtmlResult { - use_suspend(100)?; + use_suspend(Duration::from_millis(100))?; Ok(html! { { for range.clone().map(|i| html!{
{i}
} @@ -954,10 +954,9 @@ async fn hydration_props_blocked_until_hydrated() { } #[hook] - pub fn use_suspend(_wait: u64) -> SuspensionResult<()> { + pub fn use_suspend(_dur: Duration) -> SuspensionResult<()> { yew::suspense::use_future(|| async move { - #[cfg(target_arch = "wasm32")] - gloo::timers::future::sleep(Duration::from_millis(_wait)).await; + sleep(_dur).await; })?; Ok(()) @@ -965,14 +964,14 @@ async fn hydration_props_blocked_until_hydrated() { let s = ServerRenderer::::new().render().await; - gloo::utils::document() + let output_element = gloo::utils::document() .query_selector("#output") .unwrap() - .unwrap() - .set_inner_html(&s); + .unwrap(); - Renderer::::with_root(gloo_utils::document().get_element_by_id("output").unwrap()) - .hydrate(); + output_element.set_inner_html(&s); + + Renderer::::with_root(output_element).hydrate(); sleep(Duration::from_millis(150)).await; let result = obtain_result_by_id("output"); From 8846856abda5064e9db9b4dc456755c52d43441b Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Sun, 8 May 2022 13:05:17 +0900 Subject: [PATCH 6/7] Delay longer. --- packages/yew/tests/hydration.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/yew/tests/hydration.rs b/packages/yew/tests/hydration.rs index cf03e0a744c..f09ac852fe3 100644 --- a/packages/yew/tests/hydration.rs +++ b/packages/yew/tests/hydration.rs @@ -769,7 +769,7 @@ async fn hydration_suspense_no_flickering() { #[hook] pub fn use_suspend() -> SuspensionResult<()> { use_future(|| async { - gloo::timers::future::sleep(std::time::Duration::from_millis(100)).await; + gloo::timers::future::sleep(std::time::Duration::from_millis(200)).await; })?; Ok(()) } @@ -796,21 +796,21 @@ async fn hydration_suspense_no_flickering() { // outer still suspended. r#"
0
1
2
3
4
5
6
7
8
9
"# ); - sleep(Duration::from_millis(52)).await; + sleep(Duration::from_millis(103)).await; let result = obtain_result_by_id("output"); assert_eq!( result.as_str(), r#"
0
1
2
3
4
5
6
7
8
9
"# ); - sleep(Duration::from_millis(52)).await; + sleep(Duration::from_millis(103)).await; let result = obtain_result_by_id("output"); assert_eq!( result.as_str(), r#"
0
1
2
3
4
5
6
7
8
9
"# ); - sleep(Duration::from_millis(52)).await; + sleep(Duration::from_millis(103)).await; let result = obtain_result_by_id("output"); assert_eq!( @@ -819,7 +819,7 @@ async fn hydration_suspense_no_flickering() { r#"
0
1
2
3
4
5
6
7
8
9
"# ); - sleep(Duration::from_millis(52)).await; + sleep(Duration::from_millis(103)).await; let result = obtain_result_by_id("output"); assert_eq!( From cbe3b726f331d6ffbbd47a9a8689085cd6457f65 Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Mon, 23 May 2022 20:01:35 +0900 Subject: [PATCH 7/7] Only delay props during hydration. --- packages/yew/src/html/component/lifecycle.rs | 58 +++++++++++++++----- 1 file changed, 44 insertions(+), 14 deletions(-) diff --git a/packages/yew/src/html/component/lifecycle.rs b/packages/yew/src/html/component/lifecycle.rs index cc743c9a8a6..d65d660c3b3 100644 --- a/packages/yew/src/html/component/lifecycle.rs +++ b/packages/yew/src/html/component/lifecycle.rs @@ -158,6 +158,9 @@ pub(crate) trait Stateful { fn as_any(&self) -> &dyn Any; fn as_any_mut(&mut self) -> &mut dyn Any; + + #[cfg(feature = "hydration")] + fn mode(&self) -> RenderMode; } impl Stateful for CompStateInner @@ -180,6 +183,11 @@ where self.context.link().clone().into() } + #[cfg(feature = "hydration")] + fn mode(&self) -> RenderMode { + self.context.mode + } + fn flush_messages(&mut self) -> bool { self.context .link() @@ -221,7 +229,7 @@ pub(crate) struct ComponentState { #[cfg(feature = "csr")] has_rendered: bool, - #[cfg(feature = "csr")] + #[cfg(feature = "hydration")] pending_props: Option>, suspension: Option, @@ -265,7 +273,7 @@ impl ComponentState { #[cfg(feature = "csr")] has_rendered: false, - #[cfg(feature = "csr")] + #[cfg(feature = "hydration")] pending_props: None, comp_id, @@ -357,21 +365,42 @@ impl Runnable for PropsUpdateRunner { } } - // Only trigger changed if props were changed / next sibling has changed. - let schedule_render = if let Some(props) = props.or_else(|| state.pending_props.take()) - { - match state.has_rendered { - true => { - state.pending_props = None; - state.inner.props_changed(props) - } - false => { - state.pending_props = Some(props); + let should_render = |props: Option>, state: &mut ComponentState| -> bool { + props.map(|m| state.inner.props_changed(m)).unwrap_or(false) + }; + + #[cfg(feature = "hydration")] + let should_render_hydration = + |props: Option>, state: &mut ComponentState| -> bool { + if let Some(props) = props.or_else(|| state.pending_props.take()) { + match state.has_rendered { + true => { + state.pending_props = None; + state.inner.props_changed(props) + } + false => { + state.pending_props = Some(props); + false + } + } + } else { false } + }; + + // Only trigger changed if props were changed / next sibling has changed. + let schedule_render = { + #[cfg(feature = "hydration")] + { + if state.inner.mode() == RenderMode::Hydration { + should_render_hydration(props, state) + } else { + should_render(props, state) + } } - } else { - false + + #[cfg(not(feature = "hydration"))] + should_render(props, state) }; #[cfg(debug_assertions)] @@ -648,6 +677,7 @@ mod feat_csr { state.inner.rendered(self.first_render); } + #[cfg(feature = "hydration")] if state.pending_props.is_some() { scheduler::push_component_props_update(Box::new(PropsUpdateRunner { props: None,