Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Block props update during hydration #2665

Merged
merged 7 commits into from May 23, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
124 changes: 94 additions & 30 deletions packages/yew/src/html/component/lifecycle.rs
Expand Up @@ -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<COMP> Stateful for CompStateInner<COMP>
Expand All @@ -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()
Expand All @@ -198,7 +206,7 @@ where
};

if self.context.props != props {
self.context.props = Rc::clone(&props);
self.context.props = props;
self.component.changed(&self.context)
} else {
false
Expand All @@ -221,6 +229,8 @@ pub(crate) struct ComponentState {

#[cfg(feature = "csr")]
has_rendered: bool,
#[cfg(feature = "hydration")]
pending_props: Option<Rc<dyn Any>>,

suspension: Option<Suspension>,

Expand Down Expand Up @@ -263,6 +273,8 @@ impl ComponentState {

#[cfg(feature = "csr")]
has_rendered: false,
#[cfg(feature = "hydration")]
pending_props: None,

comp_id,
}
Expand Down Expand Up @@ -303,9 +315,9 @@ impl<COMP: BaseComponent> Runnable for CreateRunner<COMP> {

#[cfg(feature = "csr")]
pub(crate) struct PropsUpdateRunner {
pub props: Rc<dyn Any>,
pub props: Option<Rc<dyn Any>>,
pub state: Shared<Option<ComponentState>>,
pub next_sibling: NodeRef,
pub next_sibling: Option<NodeRef>,
}

#[cfg(feature = "csr")]
Expand All @@ -318,43 +330,86 @@ 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 {
// 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 {
next_sibling: ref mut current_next_sibling,
ref parent,
ref bundle,
..
} => {
bundle.shift(parent, next_sibling.clone());
*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());
*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)
}
let should_render = |props: Option<Rc<dyn Any>>, state: &mut ComponentState| -> bool {
props.map(|m| state.inner.props_changed(m)).unwrap_or(false)
};

#[cfg(feature = "ssr")]
ComponentRenderState::Ssr { .. } => {
#[cfg(debug_assertions)]
panic!("properties do not change during SSR");
#[cfg(feature = "hydration")]
let should_render_hydration =
|props: Option<Rc<dyn Any>>, 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
}
};

#[cfg(not(debug_assertions))]
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)
}
}

#[cfg(not(feature = "hydration"))]
should_render(props, state)
};

#[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 {
Expand Down Expand Up @@ -621,6 +676,15 @@ mod feat_csr {
if state.suspension.is_none() {
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,
state: self.state.clone(),
next_sibling: None,
}));
}
}
}
}
Expand Down
9 changes: 9 additions & 0 deletions packages/yew/src/html/component/mod.rs
Expand Up @@ -97,6 +97,15 @@ impl<COMP: BaseComponent> Context<COMP> {
///
/// 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;
Expand Down
4 changes: 2 additions & 2 deletions packages/yew/src/html/component/scope.rs
Expand Up @@ -423,8 +423,8 @@ mod feat_csr {
) {
scheduler::push_component_props_update(Box::new(PropsUpdateRunner {
state,
next_sibling,
props,
next_sibling: Some(next_sibling),
props: Some(props),
}));
// Not guaranteed to already have the scheduler started
scheduler::start();
Expand Down