Skip to content

Commit

Permalink
Fix contexts in Keyed and Indexed (#293)
Browse files Browse the repository at this point in the history
  • Loading branch information
lukechu10 authored Nov 5, 2021
1 parent 924ed0c commit d1a775b
Show file tree
Hide file tree
Showing 5 changed files with 97 additions and 52 deletions.
2 changes: 1 addition & 1 deletion packages/sycamore-reactive/src/effect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ impl Drop for ReactiveScope {
/// However, there can be multiple weak references to the same [`ReactiveScope`]. As such, it is
/// impossible to obtain a [`ReactiveScope`] from a [`ReactiveScopeWeak`] because that would allow
/// creating multiple [`ReactiveScope`]s.
#[derive(Default)]
#[derive(Default, Clone)]
pub struct ReactiveScopeWeak(pub(crate) Weak<RefCell<ReactiveScopeInner>>);

impl ReactiveScopeWeak {
Expand Down
12 changes: 8 additions & 4 deletions packages/sycamore-reactive/src/iter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ where
K: Eq + Hash,
U: Clone + 'static,
{
let parent_scope = current_scope();

// Previous state used for diffing.
let mut items = Rc::new(Vec::new());
let mapped = Rc::new(RefCell::new(Vec::new()));
Expand All @@ -47,7 +49,7 @@ where
} else if items.is_empty() {
// Fast path for new create.
for new_item in new_items.iter() {
let new_scope = create_root(|| {
let new_scope = create_child_scope_in(parent_scope.as_ref(), || {
mapped.borrow_mut().push(map_fn(new_item));
});
scopes.push(Some(Rc::new(new_scope)));
Expand Down Expand Up @@ -141,7 +143,7 @@ where
} else {
// Create new value.
let mut new_mapped = None;
let new_scope = create_root(|| {
let new_scope = create_child_scope_in(parent_scope.as_ref(), || {
new_mapped = Some(map_fn(&new_items[j]));
});

Expand Down Expand Up @@ -191,6 +193,8 @@ where
T: PartialEq + Clone,
U: Clone + 'static,
{
let parent_scope = current_scope();

// Previous state used for diffing.
let mut items = Rc::new(Vec::new());
let mapped = Rc::new(RefCell::new(Vec::new()));
Expand All @@ -216,12 +220,12 @@ where
let item = items.get(i);

if item.is_none() {
let new_scope = create_root(|| {
let new_scope = create_child_scope_in(parent_scope.as_ref(), || {
mapped.borrow_mut().push(map_fn(new_item));
});
scopes.push(new_scope);
} else if item != Some(new_item) {
let new_scope = create_root(|| {
let new_scope = create_child_scope_in(parent_scope.as_ref(),|| {
mapped.borrow_mut()[i] = map_fn(new_item);
});
scopes[i] = new_scope;
Expand Down
52 changes: 49 additions & 3 deletions packages/sycamore-reactive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,44 @@ pub use signal::*;

use wasm_bindgen::prelude::*;

/// Creates a new reactive root / scope. Generally, you won't need this method as it is called
/// automatically in `render`.
///
/// # Example
/// ```
/// use sycamore_reactive::*;
///
/// let trigger = Signal::new(());
/// let counter = Signal::new(0);
///
/// let scope = create_scope(cloned!((trigger, counter) => move || {
/// create_effect(move || {
/// trigger.get(); // subscribe to trigger
/// counter.set(*counter.get_untracked() + 1);
/// });
/// }));
///
/// assert_eq!(*counter.get(), 1);
///
/// trigger.set(());
/// assert_eq!(*counter.get(), 2);
///
/// drop(scope);
/// trigger.set(());
/// assert_eq!(*counter.get(), 2); // should not be updated because scope was dropped
/// ```
#[must_use = "create_scope returns the reactive scope of the effects created inside this scope"]
pub fn create_scope<'a>(callback: impl FnOnce() + 'a) -> ReactiveScope {
_create_child_scope_in(None, Box::new(callback))
}

pub fn create_child_scope_in<'a>(
parent: Option<&ReactiveScopeWeak>,
callback: impl FnOnce() + 'a,
) -> ReactiveScope {
_create_child_scope_in(parent, Box::new(callback))
}

/// Creates a new reactive root / scope. Generally, you won't need this method as it is called
/// automatically in `render`.
///
Expand All @@ -41,18 +79,26 @@ use wasm_bindgen::prelude::*;
/// trigger.set(());
/// assert_eq!(*counter.get(), 2); // should not be updated because scope was dropped
/// ```
/// TODO: deprecate this method in favor of [`create_scope`].
#[must_use = "create_root returns the reactive scope of the effects created inside this scope"]
pub fn create_root<'a>(callback: impl FnOnce() + 'a) -> ReactiveScope {
_create_root(Box::new(callback))
_create_child_scope_in(None, Box::new(callback))
}

/// Internal implementation: use dynamic dispatch to reduce code bloat.
fn _create_root<'a>(callback: Box<dyn FnOnce() + 'a>) -> ReactiveScope {
fn _create_child_scope_in<'a>(
parent: Option<&ReactiveScopeWeak>,
callback: Box<dyn FnOnce() + 'a>,
) -> ReactiveScope {
SCOPES.with(|scopes| {
// Push new empty scope on the stack.
let scope = ReactiveScope::new();

if let Some(parent) = scopes.borrow().last() {
// If `parent` was specified, use it as the parent of the new scope. Else use the parent of
// the scope this function is called in.
if let Some(parent) = parent {
scope.0.borrow_mut().parent = parent.clone();
} else if let Some(parent) = scopes.borrow().last() {
scope.0.borrow_mut().parent = parent.downgrade();
}
scopes.borrow_mut().push(scope);
Expand Down
75 changes: 35 additions & 40 deletions packages/sycamore/src/builder/agnostic/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,14 @@ pub mod prelude {
/// Create [`NodeBuilder`] to create UI elements.
///
/// # Example
/// ```no_run
/// node("div").build();
/// node("a").build();
/// ```
/// # use sycamore::prelude::*;
/// # fn _test<G: GenericNode>() -> Template<G> {
/// node("div").build()
/// # }
/// # fn _test2<G: GenericNode>() -> Template<G> {
/// node("a").build()
/// # }
/// ```
pub fn node<G>(tag: &'static str) -> NodeBuilder<G>
where
Expand All @@ -39,14 +44,15 @@ where
/// # Example
/// ```
/// use sycamore::prelude::*;
///
/// # use sycamore::builder::html::*;
/// #[component(MyComponent<G>)]
/// fn my_component() -> Template<G> {
/// h1().text("I am a component").build()
/// }
///
/// // Elsewhere in another component.
/// # fn view<G: GenericNode>() -> Template<G> {
/// component::<_, MyComponent<_>>(())
/// component::<_, MyComponent<_>>(())
/// # }
/// ```
pub fn component<G, C>(props: C::Props) -> Template<G>
Expand All @@ -62,7 +68,7 @@ where
/// # Example
/// ```
/// # use sycamore::prelude::*;
///
/// # use sycamore::builder::html::*;
/// # fn _test<G: GenericNode>() -> Template<G> {
/// fragment([
/// div().build(),
Expand All @@ -78,7 +84,7 @@ where
}

/// The main type powering the builder API.
#[cfg_attr(debug_assertions, derive(Debug))]
#[derive(Debug)]
pub struct NodeBuilder<G>
where
G: GenericNode,
Expand All @@ -90,12 +96,12 @@ impl<G> NodeBuilder<G>
where
G: GenericNode,
{
/// Add a child [`Template`]
/// Add a child [`Template`].
///
/// # Example
/// ```
/// # use sycamore::prelude::*;
///
/// # use sycamore::builder::html::*;
/// # fn _test<G: GenericNode>() -> Template<G> {
/// div()
/// .child(h1().text("I am a child").build())
Expand All @@ -113,11 +119,11 @@ where
/// # Example
/// ```
/// # use sycamore::prelude::*;
///
/// # use sycamore::builder::html::*;
/// # fn _test<G: GenericNode>() -> Template<G> {
/// let visible = Signal::new(true);
///
/// div()
/// div()
/// .dyn_child(
/// move || {
/// if *visible.get() {
Expand All @@ -139,7 +145,7 @@ where
/// # Example
/// ```
/// # use sycamore::prelude::*;
///
/// # use sycamore::builder::html::*;
/// # fn _test<G: GenericNode>() -> Template<G> {
/// h1().text("I am text").build()
/// }
Expand All @@ -155,7 +161,7 @@ where
/// # Example
/// ```
/// # use sycamore::prelude::*;
///
/// # use sycamore::builder::html::*;
/// # fn _test<G: GenericNode>() -> Template<G> {
/// let required = Signal::new(false);
///
Expand Down Expand Up @@ -185,7 +191,7 @@ where
/// # Example
/// ```
/// use sycamore::prelude::*;
///
/// # use sycamore::builder::html::*;
/// #[component(MyComponent<G>)]
/// fn my_component() -> Template<G> {
/// h1().text("My component").build()
Expand All @@ -204,12 +210,12 @@ where
self
}

/// Convinience function for adding an `id` to a node.
/// Convenience function for adding an `id` to a node.
///
/// # Example
/// ```
/// # use sycamore::prelude::*;
///
/// # use sycamore::builder::html::*;
/// # fn _test<G: GenericNode>() -> Template<G> {
/// button().id("my-button").build()
/// # }
Expand All @@ -223,7 +229,7 @@ where
/// # Example
/// ```
/// # use sycamore::prelude::*;
///
/// # use sycamore::builder::html::*;
/// # fn _test<G: GenericNode>() -> Template<G> {
/// button().attr("type", "submit").build()
/// # }
Expand All @@ -243,7 +249,7 @@ where
/// # Example
/// ```
/// # use sycamore::prelude::*;
///
/// # use sycamore::builder::html::*;
/// # fn _test<G: GenericNode>() -> Template<G> {
/// input().bool_attr("required", true).build()
/// # }
Expand All @@ -269,7 +275,7 @@ where
/// # Example
/// ```
/// # use sycamore::prelude::*;
///
/// # use sycamore::builder::html::*;
/// # fn _test<G: GenericNode>() -> Template<G> {
/// let input_type = Signal::new(Some("text"));
///
Expand Down Expand Up @@ -305,7 +311,7 @@ where
/// # Example
/// ```
/// # use sycamore::prelude::*;
///
/// # use sycamore::builder::html::*;
/// # fn _test<G: GenericNode>() -> Template<G> {
/// let required = Signal::new(true);
///
Expand Down Expand Up @@ -339,7 +345,7 @@ where
/// # Example
/// ```
/// # use sycamore::prelude::*;
///
/// # use sycamore::builder::html::*;
/// # fn _test<G: GenericNode>() -> Template<G> {
/// input().prop("value", "I am the value set.").build()
/// # }
Expand All @@ -362,7 +368,7 @@ where
/// # Example
/// ```
/// # use sycamore::prelude::*;
///
/// # use sycamore::builder::html::*;
/// # fn _test<G: GenericNode>() -> Template<G> {
/// let checked = Signal::new(Some(false));
///
Expand Down Expand Up @@ -399,7 +405,7 @@ where
/// # Example
/// ```
/// # use sycamore::prelude::*;
///
/// # use sycamore::builder::html::*;
/// # fn _test<G: GenericNode>() -> Template<G> {
/// button().class("bg-green-500").text("My Button").build()
/// # }
Expand All @@ -418,7 +424,7 @@ where
/// # Example
/// ```
/// # use sycamore::prelude::*;
///
/// # use sycamore::builder::html::*;
/// # fn _test<G: GenericNode>() -> Template<G> {
/// let checked_class = Signal::new(false);
///
Expand Down Expand Up @@ -459,23 +465,12 @@ where
self
}

// Need the ability to get attributes so one can filter out the
// applied attribute to add/remove it.
#[allow(dead_code)]
#[doc(hidden)]
fn dyn_style(
&self,
_style: (impl ToString, impl ToString),
_apply: StateHandle<bool>,
) -> &Self {
todo!("Implement set_dyn_style");
}

/// Adds an event listener to the node.
///
/// # Example
/// ```
/// # use sycamore::prelude::*;
/// # use sycamore::builder::html::*;
/// # fn _test<G: GenericNode>() -> Template<G> {
/// button()
/// .text("My Button")
Expand Down Expand Up @@ -503,7 +498,7 @@ where
/// # Example
/// ```
/// # use sycamore::prelude::*;
///
/// # use sycamore::builder::html::*;
/// # fn _test<G: GenericNode>() -> Template<G> {
/// let value = Signal::new(String::new());
///
Expand Down Expand Up @@ -541,7 +536,7 @@ where
/// # Example
/// ```
/// # use sycamore::prelude::*;
///
/// # use sycamore::builder::html::*;
/// # fn _test<G: GenericNode>() -> Template<G> {
/// let checked = Signal::new(false);
///
Expand Down Expand Up @@ -581,7 +576,7 @@ where
/// # Example
/// ```
/// # use sycamore::prelude::*;
///
/// # use sycamore::builder::html::*;
/// # fn _test<G: GenericNode>() -> Template<G> {
/// let node_ref = NodeRef::new();
///
Expand All @@ -604,7 +599,7 @@ where
/// # Example
/// ```
/// # use sycamore::prelude::*;
///
/// # use sycamore::builder::html::*;
/// # fn _test<G: GenericNode>() -> Template<G> {
/// input()
/// /* builder stuff */
Expand Down
Loading

0 comments on commit d1a775b

Please sign in to comment.