fix: memo with_untracked (#1213)

This commit is contained in:
Lukas Potthast 2023-06-21 16:13:18 +02:00 committed by GitHub
parent a9cbcce8b2
commit bbc7799b7c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 59 additions and 13 deletions

View file

@ -2,7 +2,7 @@
_This Code of Conduct is based on the [Rust Code of Conduct](https://www.rust-lang.org/policies/code-of-conduct)
and the [Bevy Code of Conduct](https://raw.githubusercontent.com/bevyengine/bevy/main/CODE_OF_CONDUCT.md),
which are adapted from the [Node.js Policy on Trolling](http://blog.izs.me/post/30036893703/policy-on-trolling)
which are adapted from the [Node.js Policy on Trolling](http://blog.izs.me/post/30036893703/policy-on-trolling)
and the [Contributor Covenant](https://www.contributor-covenant.org)._
## Our Pledge

View file

@ -42,7 +42,7 @@ Leptos, as a framework, reflects certain technical values:
- **Embrace Rust semantics.** Especially in things like UI templating, use Rust
semantics or extend them in a predictable way with control-flow components
rather than overloading the meaning of Rust terms like `if` or `for` in a
framework-speciic way.
framework-specific way.
- **Enhance ergonomics without obfuscating whats happening.** This is by far
the hardest to achieve. Its often the case that adding additional layers to
improve DX (like a custom build tool and starter templates) comes across as
@ -67,7 +67,7 @@ are a few guidelines that will make it a better experience for everyone:
- Our CI tests every PR against all the existing examples, sometimes requiring
compilation for both server and client side, etc. Its thorough but slow. If
you want to run CI locally to reduce frustration, you can do that by installing
`cargo-make` and using `cargo make check && cargo make test && cargo make
`cargo-make` and using `cargo make check && cargo make test && cargo make
check-examples`.
## Architecture

View file

@ -8,7 +8,6 @@
[![Discord](https://img.shields.io/discord/1031524867910148188?color=%237289DA&label=discord)](https://discord.gg/YdRAhS7eQB)
[![Matrix](https://img.shields.io/badge/Matrix-leptos-grey?logo=matrix&labelColor=white&logoColor=black)](https://matrix.to/#/#leptos:matrix.org)
[Website](https://leptos.dev) | [Book](https://leptos-rs.github.io/leptos/) | [Docs.rs](https://docs.rs/leptos/latest/leptos/) | [Playground](https://codesandbox.io/p/sandbox/leptos-rtfggt?file=%2Fsrc%2Fmain.rs%3A1%2C1) | [Discord](https://discord.gg/YdRAhS7eQB)
# Leptos

View file

@ -7,6 +7,16 @@ use crate::{
};
use std::{any::Any, cell::RefCell, fmt, marker::PhantomData, rc::Rc};
// IMPLEMENTATION NOTE:
// Memos are implemented "lazily," i.e., the inner computation is not run
// when the memo is created or when its value is marked as stale, but on demand
// when it is accessed, if the value is stale. This means that the value is stored
// internally as Option<T>, even though it can always be accessed by the user as T.
// This means the inner value can be unwrapped in circumstances in which we know
// `Runtime::update_if_necessary()` has already been called, e.g., in the
// `.try_with_no_subscription()` calls below that are unwrapped with
// `.expect("invariant: must have already been initialized")`.
/// Creates an efficient derived reactive value based on other reactive values.
///
/// Unlike a "derived signal," a memo comes with two guarantees:
@ -199,6 +209,17 @@ impl<T> PartialEq for Memo<T> {
}
}
fn forward_ref_to<T, O, F: FnOnce(&T) -> O>(
f: F,
) -> impl FnOnce(&Option<T>) -> O {
|maybe_value: &Option<T>| {
let ref_t = maybe_value
.as_ref()
.expect("invariant: must have already been initialized");
f(ref_t)
}
}
impl<T: Clone> SignalGetUntracked<T> for Memo<T> {
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
@ -215,7 +236,11 @@ impl<T: Clone> SignalGetUntracked<T> for Memo<T> {
)]
fn get_untracked(&self) -> T {
with_runtime(self.runtime, move |runtime| {
let f = move |maybe_value: &Option<T>| maybe_value.clone().unwrap();
let f = |maybe_value: &Option<T>| {
maybe_value
.clone()
.expect("invariant: must have already been initialized")
};
match self.id.try_with_no_subscription(runtime, f) {
Ok(t) => t,
Err(_) => panic_getting_dead_memo(
@ -261,10 +286,8 @@ impl<T> SignalWithUntracked<T> for Memo<T> {
)
)]
fn with_untracked<O>(&self, f: impl FnOnce(&T) -> O) -> O {
// Unwrapping here is fine for the same reasons as <Memo as
// UntrackedSignal>::get_untracked
with_runtime(self.runtime, |runtime| {
match self.id.try_with_no_subscription(runtime, |v: &T| f(v)) {
match self.id.try_with_no_subscription(runtime, forward_ref_to(f)) {
Ok(t) => t,
Err(_) => panic_getting_dead_memo(
#[cfg(any(debug_assertions, feature = "ssr"))]
@ -394,15 +417,13 @@ impl<T> SignalWith<T> for Memo<T> {
)]
#[track_caller]
fn try_with<O>(&self, f: impl FnOnce(&T) -> O) -> Option<O> {
// memo is stored as Option<T>, but will always have T available
// after latest_value() called, so we can unwrap safely
let f = move |maybe_value: &Option<T>| f(maybe_value.as_ref().unwrap());
let diagnostics = diagnostics!(self);
with_runtime(self.runtime, |runtime| {
self.id.subscribe(runtime, diagnostics);
self.id.try_with_no_subscription(runtime, f).ok()
self.id
.try_with_no_subscription(runtime, forward_ref_to(f))
.ok()
})
.ok()
.flatten()

View file

@ -13,6 +13,32 @@ fn basic_memo() {
.dispose()
}
#[cfg(not(feature = "stable"))]
#[test]
fn signal_with_untracked() {
use leptos_reactive::SignalWithUntracked;
create_scope(create_runtime(), |cx| {
let m = create_memo(cx, move |_| 5);
let copied_out = m.with_untracked(|value| *value);
assert_eq!(copied_out, 5);
})
.dispose()
}
#[cfg(not(feature = "stable"))]
#[test]
fn signal_get_untracked() {
use leptos_reactive::SignalGetUntracked;
create_scope(create_runtime(), |cx| {
let m = create_memo(cx, move |_| "memo".to_owned());
let cloned_out = m.get_untracked();
assert_eq!(cloned_out, "memo".to_owned());
})
.dispose()
}
#[cfg(not(feature = "stable"))]
#[test]
fn memo_with_computed_value() {