Skip to content

Commit

Permalink
Don't notify consumers when context hasn't changed
Browse files Browse the repository at this point in the history
  • Loading branch information
jstarry committed Jul 1, 2020
1 parent 018f048 commit bb6ca01
Show file tree
Hide file tree
Showing 2 changed files with 25 additions and 16 deletions.
25 changes: 14 additions & 11 deletions yew-functional/src/use_context_hook.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,18 @@ use yew::{Children, Component, ComponentLink, Html, Properties};
type ConsumerCallback<T> = Box<dyn Fn(Rc<T>)>;

#[derive(Clone, PartialEq, Properties)]
pub struct ContextProviderProps<T: Clone> {
pub struct ContextProviderProps<T: Clone + PartialEq> {
pub context: T,
pub children: Children,
}

pub struct ContextProvider<T: Clone + 'static> {
pub struct ContextProvider<T: Clone + PartialEq + 'static> {
context: Rc<T>,
children: Children,
consumers: RefCell<Vec<Weak<ConsumerCallback<T>>>>,
}

impl<T: Clone> ContextProvider<T> {
impl<T: Clone + PartialEq> ContextProvider<T> {
/// Add the callback to the subscriber list to be called whenever the context changes.
/// The consumer is unsubscribed as soon as the callback is dropped.
fn subscribe_consumer(&self, mut callback: Weak<ConsumerCallback<T>>) {
Expand Down Expand Up @@ -53,7 +53,7 @@ impl<T: Clone> ContextProvider<T> {
}
}

impl<T: Clone + 'static> Component for ContextProvider<T> {
impl<T: Clone + PartialEq + 'static> Component for ContextProvider<T> {
type Message = ();
type Properties = ContextProviderProps<T>;

Expand All @@ -77,8 +77,11 @@ impl<T: Clone + 'static> Component for ContextProvider<T> {
false
};

self.context = Rc::new(props.context);
self.notify_consumers();
let new_context = Rc::new(props.context);
if self.context != new_context {
self.context = new_context;
self.notify_consumers();
}

should_render
}
Expand All @@ -88,7 +91,7 @@ impl<T: Clone + 'static> Component for ContextProvider<T> {
}
}

fn find_context_provider_scope<T: 'static + Clone>(
fn find_context_provider_scope<T: Clone + PartialEq + 'static>(
scope: &AnyScope,
) -> Option<Scope<ContextProvider<T>>> {
let expected_type_id = TypeId::of::<ContextProvider<T>>();
Expand All @@ -104,24 +107,24 @@ fn with_provider_component<T, F, R>(
f: F,
) -> Option<R>
where
T: Clone,
T: Clone + PartialEq,
F: FnOnce(&ContextProvider<T>) -> R,
{
provider_scope
.as_ref()
.and_then(|scope| scope.get_component().map(|comp| f(&*comp)))
}

pub fn use_context<T: 'static + Clone>() -> Option<Rc<T>> {
pub fn use_context<T: Clone + PartialEq + 'static>() -> Option<Rc<T>> {
let scope = get_current_scope()
.expect("No current Scope. `use_context` can only be called inside functional components");

struct UseContextState<T2: 'static + Clone> {
struct UseContextState<T2: Clone + PartialEq + 'static> {
provider_scope: Option<Scope<ContextProvider<T2>>>,
current_context: Option<Rc<T2>>,
callback: Option<Rc<ConsumerCallback<T2>>>,
}
impl<T: 'static + Clone> Hook for UseContextState<T> {
impl<T: Clone + PartialEq + 'static> Hook for UseContextState<T> {
fn tear_down(&mut self) {
if let Some(cb) = self.callback.take() {
drop(cb);
Expand Down
16 changes: 11 additions & 5 deletions yew-functional/tests/use_context_hook.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ fn obtain_result(id: &str) -> String {

#[wasm_bindgen_test]
fn use_context_scoping_works() {
#[derive(Clone, Debug)]
#[derive(Clone, Debug, PartialEq)]
struct ExampleContext(String);
struct UseContextFunctionOuter {}
struct UseContextFunctionInner {}
Expand Down Expand Up @@ -178,14 +178,15 @@ fn use_context_works_with_multiple_types() {

#[wasm_bindgen_test]
fn use_context_update_works() {
#[derive(Clone, Debug)]
#[derive(Clone, Debug, PartialEq)]
struct MyContext(String);

#[derive(Clone, Debug, PartialEq, Properties)]
struct RenderCounterProps {
id: String,
children: Children,
}

struct RenderCounterFunction;
impl FunctionProvider for RenderCounterFunction {
type TProps = RenderCounterProps;
Expand Down Expand Up @@ -283,7 +284,12 @@ fn use_context_update_works() {
let app: App<TestComponent> = yew::App::new();
app.mount(yew::utils::document().get_element_by_id("output").unwrap());

assert_eq!(obtain_result("test-0"), "total: 1");
assert_eq!(obtain_result("test-1"), "current: hello world!, total: 1");
assert_eq!(obtain_result("test-2"), "current: hello world!, total: 1");
// 1 initial render + 3 update steps
assert_eq!(obtain_result("test-0"), "total: 4");

// 1 initial + 2 context update
assert_eq!(obtain_result("test-1"), "current: hello world!, total: 3");

// 1 initial + 1 context update + 1 magic update + 1 context update
assert_eq!(obtain_result("test-2"), "current: hello world!, total: 4");
}

0 comments on commit bb6ca01

Please sign in to comment.