leptos
in the wild
#2442
Replies: 3 comments
-
Hi everyone, Here are a few extension traits we use internally which might be of general-ish interest.
|
Beta Was this translation helpful? Give feedback.
-
(Converting this from an issue into a discussion simply because it's not a single, actionable item so there's not really a way to "close" it at any point. Feel free to open individual feature request issues, bug reports, or PRs as relevant — IIRC you have already made PRs!) |
Beta Was this translation helpful? Give feedback.
-
Hi again! The recent work on the /// An interface to nested signals.
///
/// Example use case: can be used to create nested routes that don't trigger full-app re-renders
/// whenever someone clicks on a link between two routes that are the same down to a certain depth.
pub trait ReactiveMerge<Other = Self>: Sized + PartialEq + Clone {
/// Merge `other` into `self`.
///
/// Returns `true` if the resulting change should be signalled higher up the tree
/// (`false` if the `self` and `other` are the same).
///
/// WARNING: should use `leptos::batch` to ensure that effects are triggered all at once at the end,
/// when the state of the tree is consistent.
fn _reactive_merge(&mut self, other: &Other) -> bool;
fn reactive_merge(&mut self, other: &Other) {
let propagate_up = self.reactive_merge(other);
assert!(!propagate_up);
}
/// Subscribe to all signals in the tree.
///
/// Calls .track() on all signals in the tree (even signals that are nested below other signals).
///
/// Example use case: allows the router to subscribe to changes deep down in the tree of routes, and push
/// updates to window.history.
fn deep_subscribe(&self);
} Example implsimpl<A, B> ReactiveMerge for (A, B)
where
A: ReactiveMerge,
B: ReactiveMerge,
{
fn _reactive_merge(&mut self, other: &Self) -> bool {
crate::_private::batch(move || {
self.0.reactive_merge(&other.0) || self.1.reactive_merge(&other.1)
})
}
fn deep_subscribe(&self) {
self.0.deep_subscribe();
self.1.deep_subscribe();
}
}
impl<T> ReactiveMerge for Option<T>
where
T: ReactiveMerge,
{
fn _reactive_merge(&mut self, other: &Self) -> bool {
match (self, other) {
(Some(old), Some(new)) => old.reactive_merge(new),
(None, None) => false,
(_old, _new) => true,
}
}
fn deep_subscribe(&self) {
if let Some(t) = self {
t.deep_subscribe();
}
}
}
impl<T> ReactiveMerge for Vec<T>
where
T: ReactiveMerge,
{
fn _reactive_merge(&mut self, other: &Self) -> bool {
// Could be smarter about this, with diffing.
crate::_private::batch(move || {
self.len() != other.len()
|| self.iter_mut().zip_eq(other).any(|(a, b)| a.reactive_merge(b))
})
}
fn deep_subscribe(&self) {
self.iter().for_each(T::deep_subscribe);
}
} And some example usage: #[derive(ReactiveMerge)]
enum MyEnum {
// The use of RcSignal here is important because it makes managing nested signals *much* easier. No scoping issues and you can still do `.into_rw` to get a `Copy` handle. See our fork if interested.
A { a: RcSignal<u8> },
B { b: MyStruct },
}
#[derive(ReactiveMerge)]
struct MyStruct {
// You have some control on which fields are editable by where the signals are placed.
field_1: Uuid,
field_2: RcSignal<u64>,
}
fn main() {
let value = RcSignal::new(MyEnum::default());
create_effect(|_| {
value.deep_subscribe();
todo!()
})
match value.get() {
MyEnum::A { a } => drop(a.get()),
MyEnum::B { b } => drop(b.field_2.set(100)),
};
value.reactive_merge(MyEnum::default());
// Imagine it does a value comparison here instead of the current `leptos` pointer comparison.
assert_eq!(value, MyEnum::default());
} As mentioned, this is related to The diffing is also important for us, but it seems like it could also be implemented on |
Beta Was this translation helpful? Give feedback.
-
Hi everyone!
We recently ported our codebase from
sycamore
toleptos
. It's still pretty early, but things have been working well.:D
I just wanted to drop in to share some things we have tweaked and/or extended in case there is interest in how things are being used in the wild and maybe upstreaming some of the more widely-applicable changes.
Our more relevant changes:
Avoid swallowing errors from event handlers. fix: re-throw errors in delegated event listeners #2382
We added an assertion to avoid delegated event handlers silently overwriting each other. fix: prevent setting the same event listener twice #2383
Signals not printing their values made debugging tricky, so we added that. The added bound didn't cause any issues for us. feat: print inner value in
Debug
impls #2384We changed
PartialEq
to compare by value.The reasoning:
sycamore
and we wanted to reduce the diff during the port. I don't think this ended up mattering much.BTreeMap
would be subscribing to unpredictable subsets of the signals inside without that being obvious.Hash
was also changed to match Giovanni-Tably@f658d6a)Exporting
ReactiveSystemError
. It was already public, just unreachable and I wanted to handle the case where an owner has been disposed of manually (to print debug warnings). Giovanni-Tably@ae31550Exposing direct access to raw
Text
nodes for some thorny ContentEditable logic. Giovanni-Tably@b4859e1A few more are visible in our fork here: Giovanni-Tably#4.
Some context:
leptos
's SSR.RwSignal
andSignal
in interfaces. Have been toying with adding a r/w equivalent ofSignal
to avoid using effects for double binding.ReactiveMerge
, which is similar to aStore
but not quite if I understand correctly. I'll write something up if there is interest, but it's essentially a way to easily manage nested signals which is useful and ergonomic for routing and other data that closely matches the render tree.Hope this is helpful!
I am happy to elaborate and open PRs to upstream anything you think makes sense.
All in all, we have been very happy with the transition, so thank you for pushing the envelope of what Rust in the FE can do!
Beta Was this translation helpful? Give feedback.
All reactions