mirror of
https://github.com/chenasraf/leptos.git
synced 2026-05-17 17:48:10 +00:00
change: move signal method implementations into traits in signal prelude (#490)
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -282,8 +282,8 @@ pub fn handle_server_fns_with_context(
|
||||
/// The provides a [MetaContext] and a [RouterIntegrationContext] to app’s 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 app’s 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:
|
||||
/// ```
|
||||
|
||||
@@ -342,8 +342,8 @@ pub type PinnedHtmlStream =
|
||||
/// The provides a [MetaContext] and a [RouterIntegrationContext] to app’s 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:
|
||||
/// ```
|
||||
|
||||
@@ -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/>`.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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/>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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)*) => {
|
||||
|
||||
@@ -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 signal’s value without
|
||||
/// cloning by applying a callback function.
|
||||
/// - [`.with_untracked()`](#impl-SignalWithUntracked<T>-for-Memo<T>) allows you to access the signal’s
|
||||
/// 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];
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
@@ -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 signal’s value without
|
||||
/// cloning by applying a callback function.
|
||||
/// - [`.with_untracked()`](#impl-SignalWithUntracked<T>-for-Signal<T>) allows you to access the signal’s
|
||||
/// 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].
|
||||
/// (There’s 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 signal’s value without
|
||||
/// cloning by applying a callback function.
|
||||
/// - [`.with_untracked()`](#impl-SignalWithUntracked<T>-for-MaybeSignal<T>) allows you to access the signal’s
|
||||
/// 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].
|
||||
/// (There’s 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];
|
||||
|
||||
@@ -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 signal’s value, and notifies all subscribers that the signal’s 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];
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use crate::{
|
||||
create_memo, IntoSignalSetter, RwSignal, Scope, Signal, SignalSetter,
|
||||
SignalUpdate, SignalWith,
|
||||
};
|
||||
|
||||
/// Derives a reactive slice of an [RwSignal](crate::RwSignal).
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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};
|
||||
|
||||
|
||||
@@ -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"))]
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user