change: move signal method implementations into traits in signal prelude (#490)

This commit is contained in:
jquesada2016
2023-02-18 06:30:03 -06:00
committed by GitHub
parent 46d6e3f78c
commit f2f52b2533
28 changed files with 1832 additions and 996 deletions

View File

@@ -1,4 +1,7 @@
use leptos::{view, Errors, For, ForProps, IntoView, RwSignal, Scope, View};
use leptos::{
signal_prelude::*, view, Errors, For, ForProps, IntoView, RwSignal, Scope,
View,
};
// A basic function to display errors served by the error boundaries. Feel free to do more complicated things
// here than just displaying them
@@ -6,21 +9,22 @@ pub fn error_template(cx: Scope, errors: Option<RwSignal<Errors>>) -> View {
let Some(errors) = errors else {
panic!("No Errors found and we expected errors!");
};
view! {cx,
<h1>"Errors"</h1>
<For
// a function that returns the items we're iterating over; a signal is fine
each=errors
// a unique key for each item as a reference
key=|(key, _)| key.clone()
// renders each item to a view
view= move |cx, (_, error)| {
let error_string = error.to_string();
view! {
cx,
<p>"Error: " {error_string}</p>
<h1>"Errors"</h1>
<For
// a function that returns the items we're iterating over; a signal is fine
each=errors
// a unique key for each item as a reference
key=|(key, _)| key.clone()
// renders each item to a view
view= move |cx, (_, error)| {
let error_string = error.to_string();
view! {
cx,
<p>"Error: " {error_string}</p>
}
}
}
/>
}
.into_view(cx)

View File

@@ -1,27 +1,33 @@
use crate::Todo;
use leptos::Scope;
use serde::{Deserialize, Serialize};
use leptos::{
signal_prelude::*,
Scope,
};
use serde::{
Deserialize,
Serialize,
};
use uuid::Uuid;
#[derive(Serialize, Deserialize)]
pub struct TodoSerialized {
pub id: Uuid,
pub title: String,
pub completed: bool,
pub id: Uuid,
pub title: String,
pub completed: bool,
}
impl TodoSerialized {
pub fn into_todo(self, cx: Scope) -> Todo {
Todo::new_with_completed(cx, self.id, self.title, self.completed)
}
pub fn into_todo(self, cx: Scope) -> Todo {
Todo::new_with_completed(cx, self.id, self.title, self.completed)
}
}
impl From<&Todo> for TodoSerialized {
fn from(todo: &Todo) -> Self {
Self {
id: todo.id,
title: todo.title.get(),
completed: todo.completed.get(),
}
fn from(todo: &Todo) -> Self {
Self {
id: todo.id,
title: todo.title.get(),
completed: todo.completed.get(),
}
}
}

View File

@@ -282,8 +282,8 @@ pub fn handle_server_fns_with_context(
/// The provides a [MetaContext] and a [RouterIntegrationContext] to apps context before
/// rendering it, and includes any meta tags injected using [leptos_meta].
///
/// The HTML stream is rendered using [render_to_stream], and includes everything described in
/// the documentation for that function.
/// The HTML stream is rendered using [render_to_stream](leptos::ssr::render_to_stream), and
/// includes everything described in the documentation for that function.
///
/// This can then be set up at an appropriate route in your application:
/// ```
@@ -602,8 +602,8 @@ where
/// The provides a [MetaContext] and a [RouterIntegrationContext] to apps context before
/// rendering it, and includes any meta tags injected using [leptos_meta].
///
/// The HTML stream is rendered using [render_to_stream], and includes everything described in
/// the documentation for that function.
/// The HTML stream is rendered using [render_to_stream](leptos::ssr::render_to_stream), and
/// includes everything described in the documentation for that function.
///
/// This can then be set up at an appropriate route in your application:
/// ```

View File

@@ -342,8 +342,8 @@ pub type PinnedHtmlStream =
/// The provides a [MetaContext] and a [RouterIntegrationContext] to apps context before
/// rendering it, and includes any meta tags injected using [leptos_meta].
///
/// The HTML stream is rendered using [render_to_stream], and includes everything described in
/// the documentation for that function.
/// The HTML stream is rendered using [render_to_stream](leptos::ssr::render_to_stream), and
/// includes everything described in the documentation for that function.
///
/// This can then be set up at an appropriate route in your application:
/// ```

View File

@@ -1,7 +1,9 @@
use crate::Children;
use leptos_dom::{Errors, IntoView};
use leptos_macro::{component, view};
use leptos_reactive::{create_rw_signal, provide_context, RwSignal, Scope};
use leptos_reactive::{
create_rw_signal, provide_context, signal_prelude::*, RwSignal, Scope,
};
/// When you render a `Result<_, _>` in your view, in the `Err` case it will
/// render nothing, and search up through the view tree for an `<ErrorBoundary/>`.

View File

@@ -1,6 +1,6 @@
use leptos::component;
use leptos_dom::{Fragment, IntoView};
use leptos_reactive::{create_memo, Scope};
use leptos_reactive::{create_memo, signal_prelude::*, Scope};
/// A component that will show its children when the `when` condition is `true`,
/// and show the fallback when it is `false`, without rerendering every time

View File

@@ -83,6 +83,8 @@ where
fallback().into_view(cx)
}
} else {
use leptos_reactive::signal_prelude::*;
// run the child; we'll probably throw this away, but it will register resource reads
let child = orig_child(cx).into_view(cx);
let after_original_child = HydrationCtx::id();

View File

@@ -147,7 +147,7 @@ fn ssr_with_styles() {
use leptos::*;
_ = create_scope(create_runtime(), |cx| {
let (value, set_value) = create_signal(cx, 0);
let (_, set_value) = create_signal(cx, 0);
let styles = "myclass";
let rendered = view! {
cx, class = styles,
@@ -170,7 +170,7 @@ fn ssr_option() {
use leptos::*;
_ = create_scope(create_runtime(), |cx| {
let (value, set_value) = create_signal(cx, 0);
let (_, _) = create_signal(cx, 0);
let rendered = view! {
cx,
<option/>

View File

@@ -1,6 +1,6 @@
use crate::{HydrationCtx, IntoView};
use cfg_if::cfg_if;
use leptos_reactive::{use_context, RwSignal};
use leptos_reactive::{signal_prelude::*, use_context, RwSignal};
use std::{collections::HashMap, error::Error, sync::Arc};
/// A struct to hold all the possible errors that could be provided by child Views

View File

@@ -1,5 +1,7 @@
use crate::{html::ElementDescriptor, HtmlElement};
use leptos_reactive::{create_effect, create_rw_signal, RwSignal, Scope};
use leptos_reactive::{
create_effect, create_rw_signal, signal_prelude::*, RwSignal, Scope,
};
use std::cell::Cell;
/// Contains a shared reference to a DOM node created while using the `view`

View File

@@ -347,7 +347,7 @@ pub fn view(tokens: TokenStream) -> TokenStream {
}
/// An optimized, cached template for client-side rendering. Follows the same
/// syntax as the [view](crate::macro) macro. In hydration or server-side rendering mode,
/// syntax as the [view!] macro. In hydration or server-side rendering mode,
/// behaves exactly as the `view` macro. In client-side rendering mode, uses a `<template>`
/// node to efficiently render the element. Should only be used with a single root element.
#[proc_macro_error::proc_macro_error]

View File

@@ -69,6 +69,8 @@
#[cfg_attr(debug_assertions, macro_use)]
extern crate tracing;
#[macro_use]
mod signal;
mod context;
mod effect;
mod hydration;
@@ -78,7 +80,6 @@ mod runtime;
mod scope;
mod selector;
mod serialization;
mod signal;
mod signal_wrappers_read;
mod signal_wrappers_write;
mod slice;
@@ -96,7 +97,7 @@ pub use runtime::{create_runtime, RuntimeId};
pub use scope::*;
pub use selector::*;
pub use serialization::*;
pub use signal::*;
pub use signal::{prelude as signal_prelude, *};
pub use signal_wrappers_read::*;
pub use signal_wrappers_write::*;
pub use slice::*;
@@ -105,43 +106,6 @@ pub use spawn_microtask::*;
pub use stored_value::*;
pub use suspense::SuspenseContext;
/// Trait implemented for all signal types which you can `get` a value
/// from, such as [`ReadSignal`],
/// [`Memo`], etc., which allows getting the inner value without
/// subscribing to the current scope.
pub trait UntrackedGettableSignal<T> {
/// Gets the signal's value without creating a dependency on the
/// current scope.
fn get_untracked(&self) -> T
where
T: Clone;
/// Runs the provided closure with a reference to the current
/// value without creating a dependency on the current scope.
fn with_untracked<O>(&self, f: impl FnOnce(&T) -> O) -> O;
}
/// Trait implemented for all signal types which you can `set` the inner
/// value, such as [`WriteSignal`] and [`RwSignal`], which allows setting
/// the inner value without causing effects which depend on the signal
/// from being run.
pub trait UntrackedSettableSignal<T> {
/// Sets the signal's value without notifying dependents.
fn set_untracked(&self, new_value: T);
/// Runs the provided closure with a mutable reference to the current
/// value without notifying dependents.
fn update_untracked(&self, f: impl FnOnce(&mut T));
/// Runs the provided closure with a mutable reference to the current
/// value without notifying dependents and returns
/// the value the closure returned.
fn update_returning_untracked<U>(
&self,
f: impl FnOnce(&mut T) -> U,
) -> Option<U>;
}
mod macros {
macro_rules! debug_warn {
($($x:tt)*) => {

View File

@@ -1,5 +1,8 @@
#![forbid(unsafe_code)]
use crate::{ReadSignal, Scope, SignalError, UntrackedGettableSignal};
use crate::{
create_effect, on_cleanup, ReadSignal, Scope, SignalGet,
SignalGetUntracked, SignalStream, SignalWith, SignalWithUntracked,
};
use std::fmt::Debug;
/// Creates an efficient derived reactive value based on other reactive values.
@@ -91,6 +94,19 @@ where
/// As with [create_effect](crate::create_effect), the argument to the memo function is the previous value,
/// i.e., the current value of the memo, which will be `None` for the initial calculation.
///
/// ## Core Trait Implementations
/// - [`.get()`](#impl-SignalGet<T>-for-Memo<T>) (or calling the signal as a function) clones the current
/// value of the signal. If you call it within an effect, it will cause that effect
/// to subscribe to the signal, and to re-run whenever the value of the signal changes.
/// - [`.get_untracked()`](#impl-SignalGetUntracked<T>-for-Memo<T>) clones the value of the signal
/// without reactively tracking it.
/// - [`.with()`](#impl-SignalWith<T>-for-Memo<T>) allows you to reactively access the signals value without
/// cloning by applying a callback function.
/// - [`.with_untracked()`](#impl-SignalWithUntracked<T>-for-Memo<T>) allows you to access the signals
/// value without reactively tracking it.
/// - [`.to_stream()`](#impl-SignalStream<T>-for-Memo<T>) converts the signal to an `async` stream of values.
///
/// ## Examples
/// ```
/// # use leptos_reactive::*;
/// # fn really_expensive_computation(value: i32) -> i32 { value };
@@ -151,7 +167,7 @@ where
impl<T> Copy for Memo<T> {}
impl<T> UntrackedGettableSignal<T> for Memo<T> {
impl<T: Clone> SignalGetUntracked<T> for Memo<T> {
#[cfg_attr(
debug_assertions,
instrument(
@@ -165,15 +181,31 @@ impl<T> UntrackedGettableSignal<T> for Memo<T> {
)
)
)]
fn get_untracked(&self) -> T
where
T: Clone,
{
fn get_untracked(&self) -> T {
// Unwrapping is fine because `T` will already be `Some(T)` by
// the time this method can be called
self.0.get_untracked().unwrap()
}
#[cfg_attr(
debug_assertions,
instrument(
level = "trace",
name = "Memo::try_get_untracked()",
skip_all,
fields(
id = ?self.0.id,
defined_at = %self.1,
ty = %std::any::type_name::<T>()
)
)
)]
fn try_get_untracked(&self) -> Option<T> {
self.0.try_get_untracked().flatten()
}
}
impl<T> SignalWithUntracked<T> for Memo<T> {
#[cfg_attr(
debug_assertions,
instrument(
@@ -192,28 +224,42 @@ impl<T> UntrackedGettableSignal<T> for Memo<T> {
// UntrackedSignal>::get_untracked
self.0.with_untracked(|v| f(v.as_ref().unwrap()))
}
#[cfg_attr(
debug_assertions,
instrument(
level = "trace",
name = "Memo::try_with_untracked()",
skip_all,
fields(
id = ?self.0.id,
defined_at = %self.1,
ty = %std::any::type_name::<T>()
)
)
)]
fn try_with_untracked<O>(&self, f: impl FnOnce(&T) -> O) -> Option<O> {
self.0.try_with_untracked(|t| f(t.as_ref().unwrap()))
}
}
impl<T> Memo<T>
where
T: 'static,
{
/// Clones and returns the current value of the memo, and subscribes
/// the running effect to the memo.
/// ```
/// # use leptos_reactive::*;
/// # create_scope(create_runtime(), |cx| {
/// let (count, set_count) = create_signal(cx, 0);
/// let double_count = create_memo(cx, move |_| count() * 2);
///
/// assert_eq!(double_count.get(), 0);
/// set_count(1);
///
/// // double_count() is shorthand for double_count.get()
/// assert_eq!(double_count(), 2);
/// # }).dispose();
/// #
/// ```
/// # Examples
///
/// ```
/// # use leptos_reactive::*;
/// # create_scope(create_runtime(), |cx| {
/// let (count, set_count) = create_signal(cx, 0);
/// let double_count = create_memo(cx, move |_| count() * 2);
///
/// assert_eq!(double_count.get(), 0);
/// set_count(1);
///
/// // double_count() is shorthand for double_count.get()
/// assert_eq!(double_count(), 2);
/// # }).dispose();
/// #
/// ```
impl<T: Clone> SignalGet<T> for Memo<T> {
#[cfg_attr(
debug_assertions,
instrument(
@@ -226,38 +272,15 @@ where
)
)
)]
pub fn get(&self) -> T
where
T: Clone,
{
self.with(T::clone)
fn get(&self) -> T {
self.0.get().unwrap()
}
/// Applies a function to the current value of the memo, and subscribes
/// the running effect to this memo.
/// ```
/// # use leptos_reactive::*;
/// # create_scope(create_runtime(), |cx| {
/// let (name, set_name) = create_signal(cx, "Alice".to_string());
/// let name_upper = create_memo(cx, move |_| name().to_uppercase());
///
/// // ❌ unnecessarily clones the string
/// let first_char = move || name_upper().chars().next().unwrap();
/// assert_eq!(first_char(), 'A');
///
/// // ✅ gets the first char without cloning the `String`
/// let first_char = move || name_upper.with(|n| n.chars().next().unwrap());
/// assert_eq!(first_char(), 'A');
/// set_name("Bob".to_string());
/// assert_eq!(first_char(), 'B');
/// # }).dispose();
/// #
/// ```
#[cfg_attr(
debug_assertions,
instrument(
name = "Memo::with()",
level = "trace",
name = "Memo::try_get()",
skip_all,
fields(
id = ?self.0.id,
@@ -266,56 +289,76 @@ where
)
)
)]
pub fn with<U>(&self, f: impl FnOnce(&T) -> U) -> U {
// okay to unwrap here, because the value will *always* have initially
// been set by the effect, synchronously
self.0
.with(|n| f(n.as_ref().expect("Memo is missing its initial value")))
fn try_get(&self) -> Option<T> {
self.0.try_get().flatten()
}
}
impl<T> SignalWith<T> for Memo<T> {
#[cfg_attr(
debug_assertions,
instrument(
level = "trace",
name = "Memo::with()",
skip_all,
fields(
id = ?self.0.id,
defined_at = %self.1,
ty = %std::any::type_name::<T>()
)
)
)]
fn with<O>(&self, f: impl FnOnce(&T) -> O) -> O {
self.0.with(|t| f(t.as_ref().unwrap()))
}
pub(crate) fn try_with<U>(
#[cfg_attr(
debug_assertions,
instrument(
level = "trace",
name = "Memo::try_with()",
skip_all,
fields(
id = ?self.0.id,
defined_at = %self.1,
ty = %std::any::type_name::<T>()
)
)
)]
fn try_with<O>(&self, f: impl FnOnce(&T) -> O) -> Option<O> {
self.0.try_with(|t| f(t.as_ref().unwrap())).ok()
}
}
impl<T: Clone> SignalStream<T> for Memo<T> {
fn to_stream(
&self,
f: impl Fn(&T) -> U,
) -> Result<U, SignalError> {
self.0.try_with(|n| {
f(n.as_ref().expect("Memo is missing its initial value"))
})
}
cx: Scope,
) -> std::pin::Pin<Box<dyn futures::Stream<Item = T>>> {
let (tx, rx) = futures::channel::mpsc::unbounded();
let close_channel = tx.clone();
on_cleanup(cx, move || close_channel.close_channel());
let this = *self;
create_effect(cx, move |_| {
let _ = tx.unbounded_send(this.get());
});
Box::pin(rx)
}
}
impl<T> Memo<T>
where
T: 'static,
{
#[cfg(feature = "hydrate")]
pub(crate) fn subscribe(&self) {
self.0.subscribe()
}
}
#[cfg(not(feature = "stable"))]
impl<T> FnOnce<()> for Memo<T>
where
T: Clone,
{
type Output = T;
extern "rust-call" fn call_once(self, _args: ()) -> Self::Output {
self.get()
}
}
#[cfg(not(feature = "stable"))]
impl<T> FnMut<()> for Memo<T>
where
T: Clone,
{
extern "rust-call" fn call_mut(&mut self, _args: ()) -> Self::Output {
self.get()
}
}
#[cfg(not(feature = "stable"))]
impl<T> Fn<()> for Memo<T>
where
T: Clone,
{
extern "rust-call" fn call(&self, _args: ()) -> Self::Output {
self.get()
}
}
impl_get_fn_traits![Memo];

View File

@@ -5,8 +5,8 @@ use crate::{
runtime::{with_runtime, RuntimeId},
serialization::Serializable,
spawn::spawn_local,
use_context, Memo, ReadSignal, Scope, ScopeProperty, SuspenseContext,
WriteSignal,
use_context, Memo, ReadSignal, Scope, ScopeProperty, SignalUpdate,
SignalWith, SuspenseContext, WriteSignal,
};
use std::{
any::Any,

View File

@@ -2,7 +2,7 @@
use crate::{
hydration::SharedContext, AnyEffect, AnyResource, Effect, EffectId, Memo,
ReadSignal, ResourceId, ResourceState, RwSignal, Scope, ScopeDisposer,
ScopeId, ScopeProperty, SerializableResource, SignalId,
ScopeId, ScopeProperty, SerializableResource, SignalId, SignalUpdate,
UnserializableResource, WriteSignal,
};
use cfg_if::cfg_if;
@@ -394,7 +394,7 @@ impl Runtime {
}
}
/// Returns IDs for all [Resource]s found on any scope.
/// Returns IDs for all [resources](crate::Resource) found on any scope.
pub(crate) fn all_resources(&self) -> Vec<ResourceId> {
self.resources
.borrow()
@@ -403,7 +403,8 @@ impl Runtime {
.collect()
}
/// Returns IDs for all [Resource]s found on any scope, pending from the server.
/// Returns IDs for all [resources](crate::Resource) found on any
/// scope, pending from the server.
pub(crate) fn pending_resources(&self) -> Vec<ResourceId> {
self.resources
.borrow()

View File

@@ -1,6 +1,7 @@
#![forbid(unsafe_code)]
use crate::{
create_isomorphic_effect, create_signal, ReadSignal, Scope, WriteSignal,
create_isomorphic_effect, create_signal, ReadSignal, Scope, SignalUpdate,
WriteSignal,
};
use std::{
cell::RefCell, collections::HashMap, fmt::Debug, hash::Hash, rc::Rc,

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,8 @@
#![forbid(unsafe_code)]
use crate::{
store_value, Memo, ReadSignal, RwSignal, Scope, StoredValue,
UntrackedGettableSignal,
create_effect, on_cleanup, store_value, Memo, ReadSignal, RwSignal, Scope,
SignalGet, SignalGetUntracked, SignalStream, SignalWith,
SignalWithUntracked, StoredValue,
};
/// Helper trait for converting `Fn() -> T` closures into
@@ -27,6 +28,19 @@ where
/// rather than adding a generic `F: Fn() -> T`. Values can be access with the same
/// function call, `with()`, and `get()` APIs as other signals.
///
/// ## Core Trait Implementations
/// - [`.get()`](#impl-SignalGet<T>-for-Signal<T>) (or calling the signal as a function) clones the current
/// value of the signal. If you call it within an effect, it will cause that effect
/// to subscribe to the signal, and to re-run whenever the value of the signal changes.
/// - [`.get_untracked()`](#impl-SignalGetUntracked<T>-for-Signal<T>) clones the value of the signal
/// without reactively tracking it.
/// - [`.with()`](#impl-SignalWith<T>-for-Signal<T>) allows you to reactively access the signals value without
/// cloning by applying a callback function.
/// - [`.with_untracked()`](#impl-SignalWithUntracked<T>-for-Signal<T>) allows you to access the signals
/// value without reactively tracking it.
/// - [`.to_stream()`](#impl-SignalStream<T>-for-Signal<T>) converts the signal to an `async` stream of values.
///
/// ## Examples
/// ```rust
/// # use leptos_reactive::*;
/// # create_scope(create_runtime(), |cx| {
@@ -71,21 +85,65 @@ impl<T> Copy for Signal<T> {}
/// Please note that using `Signal::with_untracked` still clones the inner value,
/// so there's no benefit to using it as opposed to calling
/// `Signal::get_untracked`.
impl<T> UntrackedGettableSignal<T> for Signal<T>
where
T: 'static,
{
fn get_untracked(&self) -> T
where
T: Clone,
{
impl<T: Clone> SignalGetUntracked<T> for Signal<T> {
#[cfg_attr(
debug_assertions,
instrument(
level = "trace",
name = "Signal::get_untracked()",
skip_all,
fields(
defined_at = %self.defined_at,
ty = %std::any::type_name::<T>()
)
)
)]
fn get_untracked(&self) -> T {
match &self.inner {
SignalTypes::ReadSignal(s) => s.get_untracked(),
SignalTypes::Memo(m) => m.get_untracked(),
SignalTypes::DerivedSignal(cx, f) => cx.untrack(|| f.with(|f| f())),
SignalTypes::DerivedSignal(cx, f) => {
cx.untrack(|| f.with_value(|f| f()))
}
}
}
#[cfg_attr(
debug_assertions,
instrument(
level = "trace",
name = "Signal::try_get_untracked()",
skip_all,
fields(
defined_at = %self.defined_at,
ty = %std::any::type_name::<T>()
)
)
)]
fn try_get_untracked(&self) -> Option<T> {
match &self.inner {
SignalTypes::ReadSignal(s) => s.try_get_untracked(),
SignalTypes::Memo(m) => m.try_get_untracked(),
SignalTypes::DerivedSignal(cx, f) => {
cx.untrack(|| f.try_with_value(|f| f()))
}
}
}
}
impl<T> SignalWithUntracked<T> for Signal<T> {
#[cfg_attr(
debug_assertions,
instrument(
level = "trace",
name = "Signal::with_untracked()",
skip_all,
fields(
defined_at = %self.defined_at,
ty = %std::any::type_name::<T>()
)
)
)]
fn with_untracked<O>(&self, f: impl FnOnce(&T) -> O) -> O {
match &self.inner {
SignalTypes::ReadSignal(s) => s.with_untracked(f),
@@ -93,12 +151,168 @@ where
SignalTypes::DerivedSignal(cx, v_f) => {
let mut o = None;
cx.untrack(|| o = Some(f(&v_f.with(|v_f| v_f()))));
cx.untrack(|| o = Some(f(&v_f.with_value(|v_f| v_f()))));
o.unwrap()
}
}
}
#[cfg_attr(
debug_assertions,
instrument(
level = "trace",
name = "Signal::try_with_untracked()",
skip_all,
fields(
defined_at = %self.defined_at,
ty = %std::any::type_name::<T>()
)
)
)]
fn try_with_untracked<O>(&self, f: impl FnOnce(&T) -> O) -> Option<O> {
match self.inner {
SignalTypes::ReadSignal(r) => r.try_with_untracked(f),
SignalTypes::Memo(m) => m.try_with_untracked(f),
SignalTypes::DerivedSignal(_, s) => s.try_with_value(|t| f(&t())),
}
}
}
/// # Examples
///
/// ```
/// # use leptos_reactive::*;
/// # create_scope(create_runtime(), |cx| {
/// let (name, set_name) = create_signal(cx, "Alice".to_string());
/// let name_upper =
/// Signal::derive(cx, move || name.with(|n| n.to_uppercase()));
/// let memoized_lower =
/// create_memo(cx, move |_| name.with(|n| n.to_lowercase()));
///
/// // this function takes any kind of wrapped signal
/// fn current_len_inefficient(arg: Signal<String>) -> usize {
/// // ❌ unnecessarily clones the string
/// arg().len()
/// }
///
/// fn current_len(arg: &Signal<String>) -> usize {
/// // ✅ gets the length without cloning the `String`
/// arg.with(|value| value.len())
/// }
///
/// assert_eq!(current_len(&name.into()), 5);
/// assert_eq!(current_len(&name_upper), 5);
/// assert_eq!(current_len(&memoized_lower.into()), 5);
///
/// assert_eq!(name(), "Alice");
/// assert_eq!(name_upper(), "ALICE");
/// assert_eq!(memoized_lower(), "alice");
/// # });
/// ```
impl<T> SignalWith<T> for Signal<T> {
#[cfg_attr(
debug_assertions,
instrument(
level = "trace",
name = "Signal::with()",
skip_all,
fields(
defined_at = %self.defined_at,
ty = %std::any::type_name::<T>()
)
)
)]
fn with<U>(&self, f: impl FnOnce(&T) -> U) -> U {
match &self.inner {
SignalTypes::ReadSignal(s) => s.with(f),
SignalTypes::Memo(s) => s.with(f),
SignalTypes::DerivedSignal(_, s) => f(&s.with_value(|s| s())),
}
}
#[cfg_attr(
debug_assertions,
instrument(
level = "trace",
name = "Signal::try_with()",
skip_all,
fields(
defined_at = %self.defined_at,
ty = %std::any::type_name::<T>()
)
)
)]
fn try_with<O>(&self, f: impl FnOnce(&T) -> O) -> Option<O> {
match self.inner {
SignalTypes::ReadSignal(r) => r.try_with(f).ok(),
SignalTypes::Memo(m) => m.try_with(f),
SignalTypes::DerivedSignal(_, s) => s.try_with_value(|t| f(&t())),
}
}
}
/// # Examples
///
/// ```
/// # use leptos_reactive::*;
/// # create_scope(create_runtime(), |cx| {
/// let (count, set_count) = create_signal(cx, 2);
/// let double_count = Signal::derive(cx, move || count() * 2);
/// let memoized_double_count = create_memo(cx, move |_| count() * 2);
///
/// // this function takes any kind of wrapped signal
/// fn above_3(arg: &Signal<i32>) -> bool {
/// arg.get() > 3
/// }
///
/// assert_eq!(above_3(&count.into()), false);
/// assert_eq!(above_3(&double_count), true);
/// assert_eq!(above_3(&memoized_double_count.into()), true);
/// # });
/// ```
impl<T: Clone> SignalGet<T> for Signal<T> {
fn get(&self) -> T {
match self.inner {
SignalTypes::ReadSignal(r) => r.get(),
SignalTypes::Memo(m) => m.get(),
SignalTypes::DerivedSignal(_, s) => s.with_value(|t| t()),
}
}
fn try_get(&self) -> Option<T> {
match self.inner {
SignalTypes::ReadSignal(r) => r.try_get(),
SignalTypes::Memo(m) => m.try_get(),
SignalTypes::DerivedSignal(_, s) => s.try_with_value(|t| t()),
}
}
}
impl<T: Clone> SignalStream<T> for Signal<T> {
fn to_stream(
&self,
cx: Scope,
) -> std::pin::Pin<Box<dyn futures::Stream<Item = T>>> {
match self.inner {
SignalTypes::ReadSignal(r) => r.to_stream(cx),
SignalTypes::Memo(m) => m.to_stream(cx),
SignalTypes::DerivedSignal(_, s) => {
let (tx, rx) = futures::channel::mpsc::unbounded();
let close_channel = tx.clone();
on_cleanup(cx, move || close_channel.close_channel());
create_effect(cx, move |_| {
let _ = s.try_with_value(|t| tx.unbounded_send(t()));
});
Box::pin(rx)
}
}
}
}
impl<T> Signal<T>
@@ -115,7 +329,7 @@ where
///
/// // this function takes any kind of wrapped signal
/// fn above_3(arg: &Signal<i32>) -> bool {
/// arg() > 3
/// arg.get() > 3
/// }
///
/// assert_eq!(above_3(&count.into()), false);
@@ -151,98 +365,6 @@ where
}
}
/// Applies a function to the current value of the signal, and subscribes
/// the running effect to this signal.
/// ```
/// # use leptos_reactive::*;
/// # create_scope(create_runtime(), |cx| {
/// let (name, set_name) = create_signal(cx, "Alice".to_string());
/// let name_upper = Signal::derive(cx, move || name.with(|n| n.to_uppercase()));
/// let memoized_lower = create_memo(cx, move |_| name.with(|n| n.to_lowercase()));
///
/// // this function takes any kind of wrapped signal
/// fn current_len_inefficient(arg: Signal<String>) -> usize {
/// // ❌ unnecessarily clones the string
/// arg().len()
/// }
///
/// fn current_len(arg: &Signal<String>) -> usize {
/// // ✅ gets the length without cloning the `String`
/// arg.with(|value| value.len())
/// }
///
/// assert_eq!(current_len(&name.into()), 5);
/// assert_eq!(current_len(&name_upper), 5);
/// assert_eq!(current_len(&memoized_lower.into()), 5);
///
/// assert_eq!(name(), "Alice");
/// assert_eq!(name_upper(), "ALICE");
/// assert_eq!(memoized_lower(), "alice");
/// });
/// ```
#[cfg_attr(
debug_assertions,
instrument(
level = "trace",
skip_all,
fields(
defined_at = %self.defined_at,
ty = %std::any::type_name::<T>()
)
)
)]
pub fn with<U>(&self, f: impl FnOnce(&T) -> U) -> U {
match &self.inner {
SignalTypes::ReadSignal(s) => s.with(f),
SignalTypes::Memo(s) => s.with(f),
SignalTypes::DerivedSignal(_, s) => f(&s.with(|s| s())),
}
}
/// Clones and returns the current value of the signal, and subscribes
/// the running effect to this signal.
///
/// If you want to get the value without cloning it, use [ReadSignal::with].
/// (Theres no difference in behavior for derived signals: they re-run in any case.)
/// ```
/// # use leptos_reactive::*;
/// # create_scope(create_runtime(), |cx| {
/// let (count, set_count) = create_signal(cx, 2);
/// let double_count = Signal::derive(cx, move || count() * 2);
/// let memoized_double_count = create_memo(cx, move |_| count() * 2);
///
/// // this function takes any kind of wrapped signal
/// fn above_3(arg: &Signal<i32>) -> bool {
/// arg.get() > 3
/// }
///
/// assert_eq!(above_3(&count.into()), false);
/// assert_eq!(above_3(&double_count), true);
/// assert_eq!(above_3(&memoized_double_count.into()), true);
/// # });
/// ```
#[cfg_attr(
debug_assertions,
instrument(
level = "trace",
skip_all,
fields(
defined_at = %self.defined_at,
ty = %std::any::type_name::<T>()
)
)
)]
pub fn get(&self) -> T
where
T: Clone,
{
match &self.inner {
SignalTypes::ReadSignal(s) => s.get(),
SignalTypes::Memo(s) => s.get(),
SignalTypes::DerivedSignal(_, s) => s.with(|s| s()),
}
}
/// Creates a signal that yields the default value of `T` when
/// you call `.get()` or `signal()`.
pub fn default(cx: Scope) -> Self
@@ -344,43 +466,24 @@ where
impl<T> Eq for SignalTypes<T> where T: PartialEq {}
#[cfg(not(feature = "stable"))]
impl<T> FnOnce<()> for Signal<T>
where
T: Clone,
{
type Output = T;
extern "rust-call" fn call_once(self, _args: ()) -> Self::Output {
self.get()
}
}
#[cfg(not(feature = "stable"))]
impl<T> FnMut<()> for Signal<T>
where
T: Clone,
{
extern "rust-call" fn call_mut(&mut self, _args: ()) -> Self::Output {
self.get()
}
}
#[cfg(not(feature = "stable"))]
impl<T> Fn<()> for Signal<T>
where
T: Clone,
{
extern "rust-call" fn call(&self, _args: ()) -> Self::Output {
self.get()
}
}
/// A wrapper for a value that is *either* `T` or [`Signal<T>`](crate::Signal).
///
/// This allows you to create APIs that take either a reactive or a non-reactive value
/// of the same type. This is especially useful for component properties.
///
/// ## Core Trait Implementations
/// - [`.get()`](#impl-SignalGet<T>-for-MaybeSignal<T>) (or calling the signal as a function) clones the current
/// value of the signal. If you call it within an effect, it will cause that effect
/// to subscribe to the signal, and to re-run whenever the value of the signal changes.
/// - [`.get_untracked()`](#impl-SignalGetUntracked<T>-for-MaybeSignal<T>) clones the value of the signal
/// without reactively tracking it.
/// - [`.with()`](#impl-SignalWith<T>-for-MaybeSignal<T>) allows you to reactively access the signals value without
/// cloning by applying a callback function.
/// - [`.with_untracked()`](#impl-SignalWithUntracked<T>-for-MaybeSignal<T>) allows you to access the signals
/// value without reactively tracking it.
/// - [`.to_stream()`](#impl-SignalStream<T>-for-MaybeSignal<T>) converts the signal to an `async` stream of values.
///
/// ## Examples
/// ```rust
/// # use leptos_reactive::*;
/// # create_scope(create_runtime(), |cx| {
@@ -419,24 +522,157 @@ impl<T: Default> Default for MaybeSignal<T> {
}
}
impl<T> UntrackedGettableSignal<T> for MaybeSignal<T>
where
T: 'static,
{
fn get_untracked(&self) -> T
where
T: Clone,
{
/// # Examples
///
/// ```
/// # use leptos_reactive::*;
/// # create_scope(create_runtime(), |cx| {
/// let (count, set_count) = create_signal(cx, 2);
/// let double_count = MaybeSignal::derive(cx, move || count() * 2);
/// let memoized_double_count = create_memo(cx, move |_| count() * 2);
/// let static_value: MaybeSignal<i32> = 5.into();
///
/// // this function takes any kind of wrapped signal
/// fn above_3(arg: &MaybeSignal<i32>) -> bool {
/// arg.get() > 3
/// }
///
/// assert_eq!(above_3(&count.into()), false);
/// assert_eq!(above_3(&double_count), true);
/// assert_eq!(above_3(&memoized_double_count.into()), true);
/// assert_eq!(above_3(&static_value.into()), true);
/// # });
/// ```
impl<T: Clone> SignalGet<T> for MaybeSignal<T> {
fn get(&self) -> T {
match self {
Self::Static(t) => t.clone(),
Self::Dynamic(s) => s.get(),
}
}
fn try_get(&self) -> Option<T> {
match self {
Self::Static(t) => Some(t.clone()),
Self::Dynamic(s) => s.try_get(),
}
}
}
/// # Examples
///
/// ```
/// # use leptos_reactive::*;
/// # create_scope(create_runtime(), |cx| {
/// let (name, set_name) = create_signal(cx, "Alice".to_string());
/// let name_upper =
/// MaybeSignal::derive(cx, move || name.with(|n| n.to_uppercase()));
/// let memoized_lower =
/// create_memo(cx, move |_| name.with(|n| n.to_lowercase()));
/// let static_value: MaybeSignal<String> = "Bob".to_string().into();
///
/// // this function takes any kind of wrapped signal
/// fn current_len_inefficient(arg: &MaybeSignal<String>) -> usize {
/// // ❌ unnecessarily clones the string
/// arg().len()
/// }
///
/// fn current_len(arg: &MaybeSignal<String>) -> usize {
/// // ✅ gets the length without cloning the `String`
/// arg.with(|value| value.len())
/// }
///
/// assert_eq!(current_len(&name.into()), 5);
/// assert_eq!(current_len(&name_upper), 5);
/// assert_eq!(current_len(&memoized_lower.into()), 5);
/// assert_eq!(current_len(&static_value), 3);
///
/// assert_eq!(name(), "Alice");
/// assert_eq!(name_upper(), "ALICE");
/// assert_eq!(memoized_lower(), "alice");
/// assert_eq!(static_value(), "Bob");
/// # });
/// ```
impl<T> SignalWith<T> for MaybeSignal<T> {
#[cfg_attr(
debug_assertions,
instrument(
level = "trace",
name = "MaybeSignal::with()",
skip_all,
fields(ty = %std::any::type_name::<T>())
)
)]
fn with<O>(&self, f: impl FnOnce(&T) -> O) -> O {
match self {
Self::Static(t) => f(t),
Self::Dynamic(s) => s.with(f),
}
}
#[cfg_attr(
debug_assertions,
instrument(
level = "trace",
name = "MaybeSignal::try_with()",
skip_all,
fields(ty = %std::any::type_name::<T>())
)
)]
fn try_with<O>(&self, f: impl FnOnce(&T) -> O) -> Option<O> {
match self {
Self::Static(t) => Some(f(t)),
Self::Dynamic(s) => s.try_with(f),
}
}
}
impl<T> SignalWithUntracked<T> for MaybeSignal<T> {
fn with_untracked<O>(&self, f: impl FnOnce(&T) -> O) -> O {
match self {
Self::Static(t) => f(t),
Self::Dynamic(s) => s.with_untracked(f),
}
}
fn try_with_untracked<O>(&self, f: impl FnOnce(&T) -> O) -> Option<O> {
match self {
Self::Static(t) => Some(f(t)),
Self::Dynamic(s) => s.try_with_untracked(f),
}
}
}
impl<T: Clone> SignalGetUntracked<T> for MaybeSignal<T> {
fn get_untracked(&self) -> T {
match self {
Self::Static(t) => t.clone(),
Self::Dynamic(s) => s.get_untracked(),
}
}
fn with_untracked<O>(&self, f: impl FnOnce(&T) -> O) -> O {
fn try_get_untracked(&self) -> Option<T> {
match self {
Self::Static(t) => f(t),
Self::Dynamic(s) => s.with_untracked(f),
Self::Static(t) => Some(t.clone()),
Self::Dynamic(s) => s.try_get_untracked(),
}
}
}
impl<T: Clone> SignalStream<T> for MaybeSignal<T> {
fn to_stream(
&self,
cx: Scope,
) -> std::pin::Pin<Box<dyn futures::Stream<Item = T>>> {
match self {
Self::Static(t) => {
let t = t.clone();
let stream = futures::stream::once(async move { t });
Box::pin(stream)
}
Self::Dynamic(s) => s.to_stream(cx),
}
}
}
@@ -454,12 +690,13 @@ where
/// let double_count = Signal::derive(cx, move || count() * 2);
///
/// // this function takes any kind of wrapped signal
/// fn above_3(arg: &Signal<i32>) -> bool {
/// arg() > 3
/// fn above_3(arg: &MaybeSignal<i32>) -> bool {
/// arg.get() > 3
/// }
///
/// assert_eq!(above_3(&count.into()), false);
/// assert_eq!(above_3(&double_count), true);
/// assert_eq!(above_3(&double_count.into()), true);
/// assert_eq!(above_3(&2.into()), false);
/// # });
/// ```
#[cfg_attr(
@@ -477,97 +714,6 @@ where
pub fn derive(cx: Scope, derived_signal: impl Fn() -> T + 'static) -> Self {
Self::Dynamic(Signal::derive(cx, derived_signal))
}
/// Applies a function to the current value of the signal, and subscribes
/// the running effect to this signal.
/// ```
/// # use leptos_reactive::*;
/// # create_scope(create_runtime(), |cx| {
/// let (name, set_name) = create_signal(cx, "Alice".to_string());
/// let name_upper = MaybeSignal::derive(cx, move || name.with(|n| n.to_uppercase()));
/// let memoized_lower = create_memo(cx, move |_| name.with(|n| n.to_lowercase()));
/// let static_value: MaybeSignal<String> = "Bob".to_string().into();
///
/// // this function takes any kind of wrapped signal
/// fn current_len_inefficient(arg: &MaybeSignal<String>) -> usize {
/// // ❌ unnecessarily clones the string
/// arg().len()
/// }
///
/// fn current_len(arg: &MaybeSignal<String>) -> usize {
/// // ✅ gets the length without cloning the `String`
/// arg.with(|value| value.len())
/// }
///
/// assert_eq!(current_len(&name.into()), 5);
/// assert_eq!(current_len(&name_upper), 5);
/// assert_eq!(current_len(&memoized_lower.into()), 5);
/// assert_eq!(current_len(&static_value), 3);
///
/// assert_eq!(name(), "Alice");
/// assert_eq!(name_upper(), "ALICE");
/// assert_eq!(memoized_lower(), "alice");
/// assert_eq!(static_value(), "Bob");
/// });
/// ```
#[cfg_attr(
debug_assertions,
instrument(
level = "trace",
name = "MaybeSignal::derive()",
skip_all,
fields(ty = %std::any::type_name::<T>())
)
)]
pub fn with<U>(&self, f: impl FnOnce(&T) -> U) -> U {
match &self {
Self::Static(value) => f(value),
Self::Dynamic(signal) => signal.with(f),
}
}
/// Clones and returns the current value of the signal, and subscribes
/// the running effect to this signal.
///
/// If you want to get the value without cloning it, use [ReadSignal::with].
/// (Theres no difference in behavior for derived signals: they re-run in any case.)
/// ```
/// # use leptos_reactive::*;
/// # create_scope(create_runtime(), |cx| {
/// let (count, set_count) = create_signal(cx, 2);
/// let double_count = MaybeSignal::derive(cx, move || count() * 2);
/// let memoized_double_count = create_memo(cx, move |_| count() * 2);
/// let static_value: MaybeSignal<i32> = 5.into();
///
/// // this function takes any kind of wrapped signal
/// fn above_3(arg: &MaybeSignal<i32>) -> bool {
/// arg.get() > 3
/// }
///
/// assert_eq!(above_3(&count.into()), false);
/// assert_eq!(above_3(&double_count), true);
/// assert_eq!(above_3(&memoized_double_count.into()), true);
/// assert_eq!(above_3(&static_value.into()), true);
/// # });
/// ```
#[cfg_attr(
debug_assertions,
instrument(
level = "trace",
name = "MaybeSignal::derive()",
skip_all,
fields(ty = %std::any::type_name::<T>())
)
)]
pub fn get(&self) -> T
where
T: Clone,
{
match &self {
Self::Static(value) => value.clone(),
Self::Dynamic(signal) => signal.get(),
}
}
}
impl<T> From<T> for MaybeSignal<T> {
@@ -606,34 +752,4 @@ impl From<&str> for MaybeSignal<String> {
}
}
#[cfg(not(feature = "stable"))]
impl<T> FnOnce<()> for MaybeSignal<T>
where
T: Clone,
{
type Output = T;
extern "rust-call" fn call_once(self, _args: ()) -> Self::Output {
self.get()
}
}
#[cfg(not(feature = "stable"))]
impl<T> FnMut<()> for MaybeSignal<T>
where
T: Clone,
{
extern "rust-call" fn call_mut(&mut self, _args: ()) -> Self::Output {
self.get()
}
}
#[cfg(not(feature = "stable"))]
impl<T> Fn<()> for MaybeSignal<T>
where
T: Clone,
{
extern "rust-call" fn call(&self, _args: ()) -> Self::Output {
self.get()
}
}
impl_get_fn_traits![Signal, MaybeSignal];

View File

@@ -1,5 +1,7 @@
#![forbid(unsafe_code)]
use crate::{store_value, RwSignal, Scope, StoredValue, WriteSignal};
use crate::{
store_value, RwSignal, Scope, SignalSet, StoredValue, WriteSignal,
};
/// Helper trait for converting `Fn(T)` into [`SignalSetter<T>`].
pub trait IntoSignalSetter<T>: Sized {
@@ -24,6 +26,12 @@ where
/// rather than adding a generic `F: Fn(T)`. Values can be set with the same
/// function call or `set()`, API as other signals.
///
/// ## Core Trait Implementations
/// - [`.set()`](#impl-SignalSet<T>-for-SignalSetter<T>) (or calling the setter as a function)
/// sets the signals value, and notifies all subscribers that the signals value has changed.
/// to subscribe to the signal, and to re-run whenever the value of the signal changes.
///
/// ## Examples
/// ```rust
/// # use leptos_reactive::*;
/// # create_scope(create_runtime(), |cx| {
@@ -76,6 +84,33 @@ impl<T: Default + 'static> Default for SignalSetter<T> {
impl<T> Copy for SignalSetter<T> {}
impl<T> SignalSet<T> for SignalSetter<T> {
fn set(&self, new_value: T) {
match self.inner {
SignalSetterTypes::Default => {}
SignalSetterTypes::Write(w) => w.set(new_value),
SignalSetterTypes::Mapped(_, s) => {
s.with_value(|setter| setter(new_value))
}
}
}
fn try_set(&self, new_value: T) -> Option<T> {
match self.inner {
SignalSetterTypes::Default => Some(new_value),
SignalSetterTypes::Write(w) => w.try_set(new_value),
SignalSetterTypes::Mapped(_, s) => {
let mut new_value = Some(new_value);
let _ = s
.try_with_value(|setter| setter(new_value.take().unwrap()));
new_value
}
}
}
}
impl<T> SignalSetter<T>
where
T: 'static,
@@ -157,7 +192,7 @@ where
pub fn set(&self, value: T) {
match &self.inner {
SignalSetterTypes::Write(s) => s.set(value),
SignalSetterTypes::Mapped(_, s) => s.with(|s| s(value)),
SignalSetterTypes::Mapped(_, s) => s.with_value(|s| s(value)),
SignalSetterTypes::Default => {}
}
}
@@ -236,34 +271,4 @@ where
impl<T> Eq for SignalSetterTypes<T> where T: PartialEq {}
#[cfg(not(feature = "stable"))]
impl<T> FnOnce<(T,)> for SignalSetter<T>
where
T: 'static,
{
type Output = ();
extern "rust-call" fn call_once(self, args: (T,)) -> Self::Output {
self.set(args.0)
}
}
#[cfg(not(feature = "stable"))]
impl<T> FnMut<(T,)> for SignalSetter<T>
where
T: 'static,
{
extern "rust-call" fn call_mut(&mut self, args: (T,)) -> Self::Output {
self.set(args.0)
}
}
#[cfg(not(feature = "stable"))]
impl<T> Fn<(T,)> for SignalSetter<T>
where
T: 'static,
{
extern "rust-call" fn call(&self, args: (T,)) -> Self::Output {
self.set(args.0)
}
}
impl_set_fn_traits![SignalSetter];

View File

@@ -1,5 +1,6 @@
use crate::{
create_memo, IntoSignalSetter, RwSignal, Scope, Signal, SignalSetter,
SignalUpdate, SignalWith,
};
/// Derives a reactive slice of an [RwSignal](crate::RwSignal).

View File

@@ -1,7 +1,7 @@
#![forbid(unsafe_code)]
use crate::{
create_rw_signal, RwSignal, Scope, UntrackedGettableSignal,
UntrackedSettableSignal,
create_rw_signal, RwSignal, Scope, SignalGetUntracked, SignalSetUntracked,
SignalUpdateUntracked, SignalWithUntracked,
};
/// A **non-reactive** wrapper for any value, which can be created with [store_value].
@@ -26,21 +26,21 @@ impl<T> Clone for StoredValue<T> {
impl<T> Copy for StoredValue<T> {}
impl<T> StoredValue<T>
where
T: 'static,
{
/// Clones and returns the current stored value.
impl<T> StoredValue<T> {
/// Returns a clone of the signals current value, subscribing the effect
/// to this signal.
///
/// If you want to get the value without cloning it, use [StoredValue::with].
/// (`value.get()` is equivalent to `value.with(T::clone)`.)
/// # Panics
/// Panics if you try to access a value stored in a [Scope] that has been disposed.
///
/// # Examples
/// ```
/// # use leptos_reactive::*;
/// # create_scope(create_runtime(), |cx| {
///
/// #[derive(Clone)]
/// pub struct MyCloneableData {
/// pub value: String
/// pub value: String,
/// }
/// let data = store_value(cx, MyCloneableData { value: "a".into() });
///
@@ -48,16 +48,77 @@ where
/// assert_eq!(data.get().value, "a");
/// // there's a short-hand getter form
/// assert_eq!(data().value, "a");
/// });
/// # });
/// ```
#[track_caller]
#[deprecated = "Please use `get_value` instead, as this method does not \
track the stored value. This method will also be removed \
in a future version of `leptos`"]
pub fn get(&self) -> T
where
T: Clone,
{
self.with(T::clone)
self.get_value()
}
/// Returns a clone of the signals current value, subscribing the effect
/// to this signal.
///
/// # Panics
/// Panics if you try to access a value stored in a [Scope] that has been disposed.
///
/// # Examples
/// ```
/// # use leptos_reactive::*;
/// # create_scope(create_runtime(), |cx| {
///
/// #[derive(Clone)]
/// pub struct MyCloneableData {
/// pub value: String,
/// }
/// let data = store_value(cx, MyCloneableData { value: "a".into() });
///
/// // calling .get() clones and returns the value
/// assert_eq!(data.get().value, "a");
/// // there's a short-hand getter form
/// assert_eq!(data().value, "a");
/// # });
/// ```
#[track_caller]
pub fn get_value(&self) -> T
where
T: Clone,
{
self.0.get_untracked()
}
/// Same as [`StoredValue::get`] but will not panic by default.
#[track_caller]
#[deprecated = "Please use `try_get_value` instead, as this method does \
not track the stored value. This method will also be \
removed in a future version of `leptos`"]
pub fn try_get(&self) -> Option<T>
where
T: Clone,
{
self.try_get_value()
}
/// Same as [`StoredValue::get`] but will not panic by default.
#[track_caller]
pub fn try_get_value(&self) -> Option<T>
where
T: Clone,
{
self.0.try_get_untracked()
}
/// Applies a function to the current stored value.
///
/// # Panics
/// Panics if you try to access a value stored in a [Scope] that has been disposed.
///
/// # Examples
/// ```
/// # use leptos_reactive::*;
/// # create_scope(create_runtime(), |cx| {
@@ -71,11 +132,58 @@ where
/// assert_eq!(data.with(|data| data.value.clone()), "a");
/// });
/// ```
#[track_caller]
#[deprecated = "Please use `with_value` instead, as this method does not \
track the stored value. This method will also be removed \
in a future version of `leptos`"]
pub fn with<U>(&self, f: impl FnOnce(&T) -> U) -> U {
self.with_value(f)
}
/// Applies a function to the current stored value.
///
/// # Panics
/// Panics if you try to access a value stored in a [Scope] that has been disposed.
///
/// # Examples
/// ```
/// # use leptos_reactive::*;
/// # create_scope(create_runtime(), |cx| {
///
/// pub struct MyUncloneableData {
/// pub value: String,
/// }
/// let data = store_value(cx, MyUncloneableData { value: "a".into() });
///
/// // calling .with() to extract the value
/// assert_eq!(data.with(|data| data.value.clone()), "a");
/// # });
/// ```
#[track_caller]
// track the stored value. This method will also be removed in \
// a future version of `leptos`"]
pub fn with_value<U>(&self, f: impl FnOnce(&T) -> U) -> U {
self.0.with_untracked(f)
}
/// Applies a function to the current value to mutate it in place.
/// Same as [`StoredValue::with`] but returns [`Some(O)]` only if
/// the signal is still valid. [`None`] otherwise.
#[deprecated = "Please use `try_with_value` instead, as this method does \
not track the stored value. This method will also be \
removed in a future version of `leptos`"]
pub fn try_with<O>(&self, f: impl FnOnce(&T) -> O) -> Option<O> {
self.try_with_value(f)
}
/// Same as [`StoredValue::with`] but returns [`Some(O)]` only if
/// the signal is still valid. [`None`] otherwise.
pub fn try_with_value<O>(&self, f: impl FnOnce(&T) -> O) -> Option<O> {
self.0.try_with_untracked(f)
}
/// Updates the stored value.
///
/// # Examples
/// ```
/// # use leptos_reactive::*;
/// # create_scope(create_runtime(), |cx| {
@@ -88,38 +196,93 @@ where
/// assert_eq!(data.with(|data| data.value.clone()), "b");
/// });
/// ```
pub fn update(&self, f: impl FnOnce(&mut T)) {
self.0.update_untracked(f);
}
/// Applies a function to the current value to mutate it in place.
/// Forwards the return value of the closure if the closure was called.
///
/// ```
/// use leptos_reactive::*;
/// # create_scope(create_runtime(), |cx| {
///
/// pub struct MyUncloneableData {
/// pub value: String
/// }
/// pub struct MyUncloneableData {
/// pub value: String,
/// }
///
/// let data = store_value(cx, MyUncloneableData { value: "a".into() });
/// let updated = data.update_returning(|data| {
/// data.value = "b".into();
/// data.value.clone()
/// });
/// let data = store_value(cx, MyUncloneableData { value: "a".into() });
/// let updated = data.update_returning(|data| {
/// data.value = "b".into();
/// data.value.clone()
/// });
///
/// assert_eq!(data.with(|data| data.value.clone()), "b");
/// assert_eq!(updated, Some(String::from("b")));
/// assert_eq!(data.with(|data| data.value.clone()), "b");
/// assert_eq!(updated, Some(String::from("b")));
/// # });
/// ```
#[track_caller]
#[deprecated = "Please use `update_value` instead, as this method does not \
track the stored value. This method will also be removed \
in a future version of `leptos`"]
pub fn update(&self, f: impl FnOnce(&mut T)) {
self.update_value(f);
}
/// Updates the stored value.
///
/// # Examples
/// ```
/// # use leptos_reactive::*;
/// # create_scope(create_runtime(), |cx| {
///
/// pub struct MyUncloneableData {
/// pub value: String
/// }
/// let data = store_value(cx, MyUncloneableData { value: "a".into() });
/// data.update(|data| data.value = "b".into());
/// assert_eq!(data.with(|data| data.value.clone()), "b");
/// });
/// ```
///
/// ```
/// use leptos_reactive::*;
/// # create_scope(create_runtime(), |cx| {
///
/// pub struct MyUncloneableData {
/// pub value: String,
/// }
///
/// let data = store_value(cx, MyUncloneableData { value: "a".into() });
/// let updated = data.update_returning(|data| {
/// data.value = "b".into();
/// data.value.clone()
/// });
///
/// assert_eq!(data.with(|data| data.value.clone()), "b");
/// assert_eq!(updated, Some(String::from("b")));
/// # });
/// ```
#[track_caller]
pub fn update_value(&self, f: impl FnOnce(&mut T)) {
self.0.update_untracked(f);
}
/// Updates the stored value.
#[track_caller]
#[deprecated = "Please use `try_update_value` instead, as this method does \
not track the stored value. This method will also be \
removed in a future version of `leptos`"]
pub fn update_returning<U>(
&self,
f: impl FnOnce(&mut T) -> U,
) -> Option<U> {
self.0.update_returning_untracked(f)
self.try_update_value(f)
}
/// Same as [`Self::update`], but returns [`Some(O)`] if the
/// signal is still valid, [`None`] otherwise.
pub fn try_update_value<O>(self, f: impl FnOnce(&mut T) -> O) -> Option<O> {
self.0.try_update_untracked(f)
}
/// Sets the stored value.
///
/// # Examples
/// ```
/// # use leptos_reactive::*;
/// # create_scope(create_runtime(), |cx| {
@@ -132,9 +295,39 @@ where
/// assert_eq!(data.with(|data| data.value.clone()), "b");
/// });
/// ```
#[track_caller]
#[deprecated = "Please use `set_value` instead, as this method does not \
track the stored value. This method will also be removed \
in a future version of `leptos`"]
pub fn set(&self, value: T) {
self.set_value(value);
}
/// Sets the stored value.
///
/// # Examples
/// ```
/// # use leptos_reactive::*;
/// # create_scope(create_runtime(), |cx| {
///
/// pub struct MyUncloneableData {
/// pub value: String,
/// }
/// let data = store_value(cx, MyUncloneableData { value: "a".into() });
/// data.set(MyUncloneableData { value: "b".into() });
/// assert_eq!(data.with(|data| data.value.clone()), "b");
/// # });
/// ```
#[track_caller]
pub fn set_value(&self, value: T) {
self.0.set_untracked(value);
}
/// Same as [`Self::set`], but returns [`None`] if the signal is
/// still valid, [`Some(T)`] otherwise.
pub fn try_set_value(&self, value: T) -> Option<T> {
self.0.try_set_untracked(value)
}
}
/// Creates a **non-reactive** wrapper for any value by storing it within
@@ -179,34 +372,4 @@ where
StoredValue(create_rw_signal(cx, value))
}
#[cfg(not(feature = "stable"))]
impl<T> FnOnce<()> for StoredValue<T>
where
T: Clone,
{
type Output = T;
extern "rust-call" fn call_once(self, _args: ()) -> Self::Output {
self.get()
}
}
#[cfg(not(feature = "stable"))]
impl<T> FnMut<()> for StoredValue<T>
where
T: Clone,
{
extern "rust-call" fn call_mut(&mut self, _args: ()) -> Self::Output {
self.get()
}
}
#[cfg(not(feature = "stable"))]
impl<T> Fn<()> for StoredValue<T>
where
T: Clone,
{
extern "rust-call" fn call(&self, _args: ()) -> Self::Output {
self.get()
}
}
impl_get_fn_traits!(StoredValue(get_value));

View File

@@ -1,7 +1,10 @@
//! Types that handle asynchronous data loading via `<Suspense/>`.
#![forbid(unsafe_code)]
use crate::{create_signal, queue_microtask, ReadSignal, Scope, WriteSignal};
use crate::{
create_signal, queue_microtask, ReadSignal, Scope, SignalUpdate,
WriteSignal,
};
use futures::Future;
use std::{borrow::Cow, pin::Pin};

View File

@@ -1,7 +1,7 @@
#[cfg(not(feature = "stable"))]
use leptos_reactive::{
create_isomorphic_effect, create_runtime, create_scope, create_signal,
UntrackedGettableSignal, UntrackedSettableSignal,
signal_prelude::*, SignalGetUntracked, SignalSetUntracked,
};
#[cfg(not(feature = "stable"))]

View File

@@ -1,7 +1,7 @@
use crate::{ServerFn, ServerFnError};
use leptos_reactive::{
create_rw_signal, spawn_local, store_value, ReadSignal, RwSignal, Scope,
StoredValue,
create_rw_signal, signal_prelude::*, spawn_local, store_value, ReadSignal,
RwSignal, Scope, StoredValue,
};
use std::{future::Future, pin::Pin, rc::Rc};
@@ -90,30 +90,30 @@ where
{
/// Calls the `async` function with a reference to the input type as its argument.
pub fn dispatch(&self, input: I) {
self.0.with(|a| a.dispatch(input))
self.0.with_value(|a| a.dispatch(input))
}
/// Whether the action has been dispatched and is currently waiting for its future to be resolved.
pub fn pending(&self) -> ReadSignal<bool> {
self.0.with(|a| a.pending.read_only())
self.0.with_value(|a| a.pending.read_only())
}
/// Updates whether the action is currently pending.
pub fn set_pending(&self, pending: bool) {
self.0.with(|a| a.pending.set(pending))
self.0.with_value(|a| a.pending.set(pending))
}
/// The URL associated with the action (typically as part of a server function.)
/// This enables integration with the `ActionForm` component in `leptos_router`.
pub fn url(&self) -> Option<String> {
self.0.with(|a| a.url.as_ref().cloned())
self.0.with_value(|a| a.url.as_ref().cloned())
}
/// Associates the URL of the given server function with this action.
/// This enables integration with the `ActionForm` component in `leptos_router`.
pub fn using_server_fn<T: ServerFn>(self) -> Self {
let prefix = T::prefix();
self.0.update(|state| {
self.0.update_value(|state| {
state.url = if prefix.is_empty() {
Some(T::url().to_string())
} else {
@@ -125,18 +125,18 @@ where
/// How many times the action has successfully resolved.
pub fn version(&self) -> RwSignal<usize> {
self.0.with(|a| a.version)
self.0.with_value(|a| a.version)
}
/// The current argument that was dispatched to the `async` function.
/// `Some` while we are waiting for it to resolve, `None` if it has resolved.
pub fn input(&self) -> RwSignal<Option<I>> {
self.0.with(|a| a.input)
self.0.with_value(|a| a.input)
}
/// The most recent return value of the `async` function.
pub fn value(&self) -> RwSignal<Option<O>> {
self.0.with(|a| a.value)
self.0.with_value(|a| a.value)
}
}

View File

@@ -1,7 +1,7 @@
use crate::{ServerFn, ServerFnError};
use leptos_reactive::{
create_rw_signal, spawn_local, store_value, ReadSignal, RwSignal, Scope,
StoredValue,
create_rw_signal, signal_prelude::*, spawn_local, store_value, ReadSignal,
RwSignal, Scope, StoredValue,
};
use std::{future::Future, pin::Pin, rc::Rc};
@@ -20,9 +20,9 @@ use std::{future::Future, pin::Pin, rc::Rc};
/// # use leptos::*;
/// # run_scope(create_runtime(), |cx| {
/// async fn send_new_todo_to_api(task: String) -> usize {
/// // do something...
/// // return a task id
/// 42
/// // do something...
/// // return a task id
/// 42
/// }
/// let add_todo = create_multi_action(cx, |task: &String| {
/// // `task` is given as `&String` because its value is available in `input`
@@ -95,30 +95,30 @@ where
{
/// Calls the `async` function with a reference to the input type as its argument.
pub fn dispatch(&self, input: I) {
self.0.with(|a| a.dispatch(input))
self.0.with_value(|a| a.dispatch(input))
}
/// The set of all submissions to this multi-action.
pub fn submissions(&self) -> ReadSignal<Vec<Submission<I, O>>> {
self.0.with(|a| a.submissions())
self.0.with_value(|a| a.submissions())
}
/// The URL associated with the action (typically as part of a server function.)
/// This enables integration with the `MultiActionForm` component in `leptos_router`.
pub fn url(&self) -> Option<String> {
self.0.with(|a| a.url.as_ref().cloned())
self.0.with_value(|a| a.url.as_ref().cloned())
}
/// How many times an action has successfully resolved.
pub fn version(&self) -> RwSignal<usize> {
self.0.with(|a| a.version)
self.0.with_value(|a| a.version)
}
/// Associates the URL of the given server function with this action.
/// This enables integration with the `MultiActionForm` component in `leptos_router`.
pub fn using_server_fn<T: ServerFn>(self) -> Self {
let prefix = T::prefix();
self.0.update(|a| {
self.0.update_value(|a| {
a.url = if prefix.is_empty() {
Some(T::url().to_string())
} else {
@@ -243,9 +243,9 @@ where
/// # use leptos::*;
/// # run_scope(create_runtime(), |cx| {
/// async fn send_new_todo_to_api(task: String) -> usize {
/// // do something...
/// // return a task id
/// 42
/// // do something...
/// // return a task id
/// 42
/// }
/// let add_todo = create_multi_action(cx, |task: &String| {
/// // `task` is given as `&String` because its value is available in `input`

View File

@@ -1,5 +1,7 @@
use crate::{use_navigate, use_resolved_path, NavigateOptions};
use leptos::{component, provide_context, use_context, IntoView, Scope};
use leptos::{
component, provide_context, signal_prelude::*, use_context, IntoView, Scope,
};
use std::rc::Rc;
/// Redirects the user to a new URL, whether on the client side or on the server

View File

@@ -22,7 +22,7 @@ pub fn Route<E, F, P>(
/// wildcard (`user/*any`).
path: P,
/// The view that should be shown when this route is matched. This can be any function
/// that takes a [Scope] and returns an [Element] (like `|cx| view! { cx, <p>"Show this"</p> })`
/// that takes a [Scope] and returns a type that implements [IntoView] (like `|cx| view! { cx, <p>"Show this"</p> })`
/// or `|cx| view! { cx, <MyComponent/>` } or even, for a component with no props, `MyComponent`).
view: F,
/// The mode that this route prefers during server-side rendering. Defaults to out-of-order streaming.

View File

@@ -2,7 +2,7 @@ use crate::{
Location, NavigateOptions, NavigationError, Params, ParamsError, ParamsMap,
RouteContext, RouterContext,
};
use leptos::{create_memo, use_context, Memo, Scope};
use leptos::{create_memo, signal_prelude::*, use_context, Memo, Scope};
use std::rc::Rc;
/// Returns the current [RouterContext], containing information about the router's state.