Skip to content

Commit

Permalink
Replace mounted with rendered lifecycle method
Browse files Browse the repository at this point in the history
  • Loading branch information
jstarry committed Apr 20, 2020
1 parent fa8a1d9 commit df6dee0
Show file tree
Hide file tree
Showing 8 changed files with 153 additions and 107 deletions.
9 changes: 5 additions & 4 deletions examples/node_refs/src/lib.rs
Expand Up @@ -28,11 +28,12 @@ impl Component for Model {
}
}

fn mounted(&mut self) -> ShouldRender {
if let Some(input) = self.refs[self.focus_index].cast::<InputElement>() {
input.focus().unwrap();
fn rendered(&mut self, first_render: bool) {
if first_render {
if let Some(input) = self.refs[self.focus_index].cast::<InputElement>() {
input.focus().unwrap();
}
}
false
}

fn update(&mut self, msg: Self::Message) -> ShouldRender {
Expand Down
27 changes: 12 additions & 15 deletions examples/webgl/src/lib.rs
@@ -1,10 +1,9 @@
use wasm_bindgen::JsCast;
use web_sys::HtmlCanvasElement;
use web_sys::WebGlRenderingContext as GL;
use yew::services::{RenderService, Task};
use yew::{html, Component, ComponentLink, Html, NodeRef, ShouldRender};

use wasm_bindgen::JsCast;

pub struct Model {
canvas: Option<HtmlCanvasElement>,
gl: Option<GL>,
Expand All @@ -31,8 +30,8 @@ impl Component for Model {
}
}

fn mounted(&mut self) -> ShouldRender {
// Once mounted, store references for the canvas and GL context. These can be used for
fn rendered(&mut self, first_render: bool) {
// Once rendered, store references for the canvas and GL context. These can be used for
// resizing the rendering area when the window or canvas element are resized, as well as
// for making GL calls.

Expand All @@ -52,18 +51,16 @@ impl Component for Model {
// done here, such as enabling or disabling depth testing, depth functions, face
// culling etc.

// The callback to request animation frame is passed a time value which can be used for
// rendering motion independent of the framerate which may vary.
let render_frame = self.link.callback(Msg::Render);
let handle = RenderService::new().request_animation_frame(render_frame);
if first_render {
// The callback to request animation frame is passed a time value which can be used for
// rendering motion independent of the framerate which may vary.
let render_frame = self.link.callback(Msg::Render);
let handle = RenderService::new().request_animation_frame(render_frame);

// A reference to the handle must be stored, otherwise it is dropped and the render won't
// occur.
self.render_loop = Some(Box::new(handle));

// Since WebGL is rendered to the canvas "separate" from the DOM, there is no need to
// render the DOM element(s) again.
false
// A reference to the handle must be stored, otherwise it is dropped and the render won't
// occur.
self.render_loop = Some(Box::new(handle));
}
}

fn update(&mut self, msg: Self::Message) -> ShouldRender {
Expand Down
17 changes: 12 additions & 5 deletions yew-functional/tests/lib.rs
Expand Up @@ -181,9 +181,16 @@ fn use_effect_destroys_on_component_drop() {
type TProps = DestroyCalledProps;

fn run(props: &Self::TProps) -> Html {
let (should_rerender, set_rerender) = use_state(|| true);
if *should_rerender {
set_rerender(false);
let (show, set_show) = use_state(|| true);
use_effect_with_deps(
move |_| {
set_show(false);
|| {}
},
(),
);

if *show {
return html! {
<UseEffectComponent destroy_called=props.destroy_called.clone() />
};
Expand All @@ -196,11 +203,11 @@ fn use_effect_destroys_on_component_drop() {
}
let app: App<UseEffectWrapperComponent> = yew::App::new();
let destroy_counter = Rc::new(std::cell::RefCell::new(0));
let destroy_country_c = destroy_counter.clone();
let destroy_counter_c = destroy_counter.clone();
app.mount_with_props(
yew::utils::document().get_element_by_id("output").unwrap(),
DestroyCalledProps {
destroy_called: Rc::new(move || *destroy_country_c.borrow_mut().deref_mut() += 1),
destroy_called: Rc::new(move || *destroy_counter_c.borrow_mut().deref_mut() += 1),
},
);
assert_eq!(1, *destroy_counter.borrow().deref());
Expand Down
9 changes: 5 additions & 4 deletions yew-stdweb/examples/node_refs/src/lib.rs
Expand Up @@ -29,11 +29,12 @@ impl Component for Model {
}
}

fn mounted(&mut self) -> ShouldRender {
if let Some(input) = self.refs[self.focus_index].cast::<InputElement>() {
input.focus();
fn rendered(&mut self, first_render: bool) {
if first_render {
if let Some(input) = self.refs[self.focus_index].cast::<InputElement>() {
input.focus();
}
}
false
}

fn update(&mut self, msg: Self::Message) -> ShouldRender {
Expand Down
24 changes: 11 additions & 13 deletions yew-stdweb/examples/webgl/src/lib.rs
Expand Up @@ -33,8 +33,8 @@ impl Component for Model {
}
}

fn mounted(&mut self) -> ShouldRender {
// Once mounted, store references for the canvas and GL context. These can be used for
fn rendered(&mut self, first_render: bool) {
// Once rendered, store references for the canvas and GL context. These can be used for
// resizing the rendering area when the window or canvas element are resized, as well as
// for making GL calls.
let c: CanvasElement = self.node_ref.cast().unwrap();
Expand All @@ -47,18 +47,16 @@ impl Component for Model {
// done here, such as enabling or disabling depth testing, depth functions, face
// culling etc.

// The callback to request animation frame is passed a time value which can be used for
// rendering motion independent of the framerate which may vary.
let render_frame = self.link.callback(Msg::Render);
let handle = RenderService::new().request_animation_frame(render_frame);

// A reference to the handle must be stored, otherwise it is dropped and the render won't
// occur.
self.render_loop = Some(Box::new(handle));
if first_render {
// The callback to request animation frame is passed a time value which can be used for
// rendering motion independent of the framerate which may vary.
let render_frame = self.link.callback(Msg::Render);
let handle = RenderService::new().request_animation_frame(render_frame);

// Since WebGL is rendered to the canvas "separate" from the DOM, there is no need to
// render the DOM element(s) again.
false
// A reference to the handle must be stored, otherwise it is dropped and the render won't
// occur.
self.render_loop = Some(Box::new(handle));
}
}

fn update(&mut self, msg: Self::Message) -> ShouldRender {
Expand Down
27 changes: 14 additions & 13 deletions yew/src/html/mod.rs
Expand Up @@ -67,14 +67,9 @@ pub trait Component: Sized + 'static {
/// Components are created with their properties as well as a `ComponentLink` which
/// can be used to send messages and create callbacks for triggering updates.
fn create(props: Self::Properties, link: ComponentLink<Self>) -> Self;
/// Called after the component has been attached to the VDOM and it is safe to receive messages
/// from agents but before the browser updates the screen. If true is returned, the view will
/// be re-rendered and the user will not see the initial render.
fn mounted(&mut self) -> ShouldRender {
false
}
/// Called everytime when a messages of `Msg` type received. It also takes a
/// reference to a context.

/// Components handle messages in their `update` method and commonly use this method
/// to update their state and (optionally) re-render themselves.
fn update(&mut self, msg: Self::Message) -> ShouldRender;

/// When the parent of a Component is re-rendered, it will either be re-created or
Expand Down Expand Up @@ -107,7 +102,12 @@ pub trait Component: Sized + 'static {
/// `html!` procedural macro. The full guide to using the macro can be found in [Yew's
/// documentation](https://yew.rs/docs/concepts/html).
fn view(&self) -> Html;
/// Called for finalization on the final point of the component's lifetime.

/// The `rendered` method is called after each time a Component is rendered but
/// before the browser updates the page.
fn rendered(&mut self, _first_render: bool) {}

/// The `destroy` method is called right before a Component is unmounted.
fn destroy(&mut self) {} // TODO(#941): Replace with `Drop`
}

Expand Down Expand Up @@ -359,11 +359,12 @@ where
/// }
/// }
///
/// fn mounted(&mut self) -> ShouldRender {
/// if let Some(input) = self.node_ref.cast::<InputElement>() {
/// input.focus();
/// fn rendered(&mut self, first_render: bool) {
/// if first_render {
/// if let Some(input) = self.node_ref.cast::<InputElement>() {
/// input.focus();
/// }
/// }
/// false
/// }
///
/// fn change(&mut self, _: Self::Properties) -> ShouldRender {
Expand Down
67 changes: 40 additions & 27 deletions yew/src/html/scope.rs
@@ -1,5 +1,5 @@
use super::*;
use crate::scheduler::{scheduler, Runnable, Shared};
use crate::scheduler::{scheduler, ComponentRunnableType, Runnable, Shared};
use crate::virtual_dom::{VDiff, VNode};
use cfg_if::cfg_if;
use std::cell::RefCell;
Expand Down Expand Up @@ -73,22 +73,15 @@ impl<COMP: Component> Scope<COMP> {
};
*scope.shared_state.borrow_mut() = ComponentState::Ready(ready_state);
scope.create();
scope.mounted();
scope
}

/// Schedules a task to call the mounted method on a component and optionally re-render
pub(crate) fn mounted(&mut self) {
let shared_state = self.shared_state.clone();
let mounted = MountedComponent { shared_state };
scheduler().push_mount(Box::new(mounted));
}

/// Schedules a task to create and render a component and then mount it to the DOM
pub(crate) fn create(&mut self) {
let shared_state = self.shared_state.clone();
let create = CreateComponent { shared_state };
scheduler().push_create(Box::new(create));
scheduler().push_comp(ComponentRunnableType::Create, Box::new(create));
self.rendered(true);
}

/// Schedules a task to send a message or new props to a component
Expand All @@ -97,14 +90,25 @@ impl<COMP: Component> Scope<COMP> {
shared_state: self.shared_state.clone(),
update,
};
scheduler().push(Box::new(update));
scheduler().push_comp(ComponentRunnableType::Update, Box::new(update));
self.rendered(false);
}

/// Schedules a task to call the rendered method on a component
pub(crate) fn rendered(&self, first_render: bool) {
let shared_state = self.shared_state.clone();
let rendered = RenderedComponent {
shared_state,
first_render,
};
scheduler().push_comp(ComponentRunnableType::Rendered, Box::new(rendered));
}

/// Schedules a task to destroy a component
pub(crate) fn destroy(&mut self) {
let shared_state = self.shared_state.clone();
let destroy = DestroyComponent { shared_state };
scheduler().push(Box::new(destroy));
scheduler().push_comp(ComponentRunnableType::Destroy, Box::new(destroy));
}

/// Send a message to the component
Expand All @@ -113,10 +117,13 @@ impl<COMP: Component> Scope<COMP> {
T: Into<COMP::Message>,
{
self.update(ComponentUpdate::Message(msg.into()));
self.rendered(false);
}

/// Send a batch of messages to the component
pub fn send_message_batch(&self, messages: Vec<COMP::Message>) {
self.update(ComponentUpdate::MessageBatch(messages));
self.rendered(false);
}

/// This method creates a `Callback` which will send a message to the linked component's
Expand Down Expand Up @@ -152,7 +159,7 @@ impl<COMP: Component> Scope<COMP> {
enum ComponentState<COMP: Component> {
Empty,
Ready(ReadyState<COMP>),
Created(CreatedState<COMP>),
Created((bool, CreatedState<COMP>)),
Processing,
Destroyed,
}
Expand Down Expand Up @@ -197,13 +204,10 @@ struct CreatedState<COMP: Component> {
}

impl<COMP: Component> CreatedState<COMP> {
/// Called once immediately after the component is created.
fn mounted(mut self) -> Self {
if self.component.mounted() {
self.update()
} else {
self
}
/// Called after a component and all of its children have been rendered.
fn rendered(mut self, first_render: bool) -> Self {
self.component.rendered(first_render);
self
}

fn update(mut self) -> Self {
Expand All @@ -222,21 +226,28 @@ impl<COMP: Component> CreatedState<COMP> {
}
}

struct MountedComponent<COMP>
struct RenderedComponent<COMP>
where
COMP: Component,
{
shared_state: Shared<ComponentState<COMP>>,
first_render: bool,
}

impl<COMP> Runnable for MountedComponent<COMP>
impl<COMP> Runnable for RenderedComponent<COMP>
where
COMP: Component,
{
fn run(self: Box<Self>) {
let current_state = self.shared_state.replace(ComponentState::Processing);
self.shared_state.replace(match current_state {
ComponentState::Created(state) => ComponentState::Created(state.mounted()),
ComponentState::Created((needs_render, state)) => {
if needs_render {
ComponentState::Created((false, state.rendered(self.first_render)))
} else {
ComponentState::Created((needs_render, state))
}
}
ComponentState::Destroyed => current_state,
ComponentState::Empty | ComponentState::Processing | ComponentState::Ready(_) => {
panic!("unexpected component state: {}", current_state);
Expand All @@ -259,7 +270,9 @@ where
fn run(self: Box<Self>) {
let current_state = self.shared_state.replace(ComponentState::Processing);
self.shared_state.replace(match current_state {
ComponentState::Ready(state) => ComponentState::Created(state.create().update()),
ComponentState::Ready(state) => {
ComponentState::Created((true, state.create().update()))
}
ComponentState::Created(_) | ComponentState::Destroyed => current_state,
ComponentState::Empty | ComponentState::Processing => {
panic!("unexpected component state: {}", current_state);
Expand All @@ -281,7 +294,7 @@ where
{
fn run(self: Box<Self>) {
match self.shared_state.replace(ComponentState::Destroyed) {
ComponentState::Created(mut this) => {
ComponentState::Created((_, mut this)) => {
this.component.destroy();
if let Some(last_frame) = &mut this.last_frame {
last_frame.detach(&this.element);
Expand Down Expand Up @@ -313,7 +326,7 @@ where
fn run(self: Box<Self>) {
let current_state = self.shared_state.replace(ComponentState::Processing);
self.shared_state.replace(match current_state {
ComponentState::Created(mut this) => {
ComponentState::Created((needs_render, mut this)) => {
let should_update = match self.update {
ComponentUpdate::Message(message) => this.component.update(message),
ComponentUpdate::MessageBatch(messages) => messages
Expand All @@ -327,7 +340,7 @@ where
}
};
let next_state = if should_update { this.update() } else { this };
ComponentState::Created(next_state)
ComponentState::Created((!needs_render && !should_update, next_state))
}
ComponentState::Destroyed => current_state,
ComponentState::Processing | ComponentState::Ready(_) | ComponentState::Empty => {
Expand Down

0 comments on commit df6dee0

Please sign in to comment.