mirror of
https://github.com/leptos-rs/leptos
synced 2024-11-10 14:54:16 +00:00
commit
7ad94cc520
10 changed files with 433 additions and 165 deletions
|
@ -90,7 +90,7 @@ pub fn Counter(cx: Scope) -> impl IntoView {
|
|||
let clear = create_action(cx, |_| clear_server_count());
|
||||
let counter = create_resource(
|
||||
cx,
|
||||
move || (dec.version.get(), inc.version.get(), clear.version.get()),
|
||||
move || (dec.version().get(), inc.version().get(), clear.version().get()),
|
||||
|_| get_server_count(),
|
||||
);
|
||||
|
||||
|
@ -130,14 +130,9 @@ pub fn FormCounter(cx: Scope) -> impl IntoView {
|
|||
|
||||
let counter = create_resource(
|
||||
cx,
|
||||
{
|
||||
let adjust = adjust.version;
|
||||
let clear = clear.version;
|
||||
move || (adjust.get(), clear.get())
|
||||
},
|
||||
move || (adjust.version().get(), clear.version().get()),
|
||||
|_| {
|
||||
log::debug!("FormCounter running fetcher");
|
||||
|
||||
get_server_count()
|
||||
},
|
||||
);
|
||||
|
@ -151,8 +146,6 @@ pub fn FormCounter(cx: Scope) -> impl IntoView {
|
|||
.unwrap_or(0)
|
||||
};
|
||||
|
||||
let adjust2 = adjust.clone();
|
||||
|
||||
view! {
|
||||
cx,
|
||||
<div>
|
||||
|
@ -172,7 +165,7 @@ pub fn FormCounter(cx: Scope) -> impl IntoView {
|
|||
<input type="submit" value="-1"/>
|
||||
</ActionForm>
|
||||
<span>"Value: " {move || value().to_string()} "!"</span>
|
||||
<ActionForm action=adjust2>
|
||||
<ActionForm action=adjust>
|
||||
<input type="hidden" name="delta" value="1"/>
|
||||
<input type="hidden" name="msg" value="form value up"/>
|
||||
<input type="submit" value="+1"/>
|
||||
|
|
|
@ -120,7 +120,7 @@ pub fn TodoApp(cx: Scope) -> impl IntoView {
|
|||
}/>
|
||||
</Routes>
|
||||
</main>
|
||||
</Router>
|
||||
</Router>
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -130,14 +130,10 @@ pub fn Todos(cx: Scope) -> impl IntoView {
|
|||
let delete_todo = create_server_action::<DeleteTodo>(cx);
|
||||
let submissions = add_todo.submissions();
|
||||
|
||||
// track mutations that should lead us to refresh the list
|
||||
let add_changed = add_todo.version;
|
||||
let todo_deleted = delete_todo.version;
|
||||
|
||||
// list of todos is loaded from the server in reaction to changes
|
||||
let todos = create_resource(
|
||||
cx,
|
||||
move || (add_changed(), todo_deleted()),
|
||||
move || (add_todo.version().get(), delete_todo.version().get()),
|
||||
move |_| get_todos(cx),
|
||||
);
|
||||
|
||||
|
@ -152,17 +148,11 @@ pub fn Todos(cx: Scope) -> impl IntoView {
|
|||
<input type="submit" value="Add"/>
|
||||
</MultiActionForm>
|
||||
<Transition fallback=move || view! {cx, <p>"Loading..."</p> }>
|
||||
{
|
||||
let delete_todo = delete_todo.clone();
|
||||
move || {
|
||||
{move || {
|
||||
let existing_todos = {
|
||||
let delete_todo = delete_todo.clone();
|
||||
move || {
|
||||
todos
|
||||
.read()
|
||||
.map({
|
||||
let delete_todo = delete_todo.clone();
|
||||
move |todos| match todos {
|
||||
todos.read()
|
||||
.map(move |todos| match todos {
|
||||
Err(e) => {
|
||||
vec![view! { cx, <pre class="error">"Server Error: " {e.to_string()}</pre>}.into_any()]
|
||||
}
|
||||
|
@ -172,29 +162,24 @@ pub fn Todos(cx: Scope) -> impl IntoView {
|
|||
} else {
|
||||
todos
|
||||
.into_iter()
|
||||
.map({
|
||||
let delete_todo = delete_todo.clone();
|
||||
move |todo| {
|
||||
let delete_todo = delete_todo.clone();
|
||||
view! {
|
||||
cx,
|
||||
<li>
|
||||
{todo.title}
|
||||
<ActionForm action=delete_todo.clone()>
|
||||
<input type="hidden" name="id" value={todo.id}/>
|
||||
<input type="submit" value="X"/>
|
||||
</ActionForm>
|
||||
</li>
|
||||
}
|
||||
.into_any()
|
||||
.map(move |todo| {
|
||||
view! {
|
||||
cx,
|
||||
<li>
|
||||
{todo.title}
|
||||
<ActionForm action=delete_todo>
|
||||
<input type="hidden" name="id" value={todo.id}/>
|
||||
<input type="submit" value="X"/>
|
||||
</ActionForm>
|
||||
</li>
|
||||
}
|
||||
.into_any()
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.unwrap_or_default()
|
||||
})
|
||||
.unwrap_or_default()
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -104,7 +104,7 @@ pub fn TodoApp(cx: Scope) -> impl IntoView {
|
|||
}/>
|
||||
</Routes>
|
||||
</main>
|
||||
</Router>
|
||||
</Router>
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -114,14 +114,10 @@ pub fn Todos(cx: Scope) -> impl IntoView {
|
|||
let delete_todo = create_server_action::<DeleteTodo>(cx);
|
||||
let submissions = add_todo.submissions();
|
||||
|
||||
// track mutations that should lead us to refresh the list
|
||||
let add_changed = add_todo.version;
|
||||
let todo_deleted = delete_todo.version;
|
||||
|
||||
// list of todos is loaded from the server in reaction to changes
|
||||
let todos = create_resource(
|
||||
cx,
|
||||
move || (add_changed(), todo_deleted()),
|
||||
move || (add_todo.version().get(), delete_todo.version().get()),
|
||||
move |_| get_todos(cx),
|
||||
);
|
||||
|
||||
|
@ -136,17 +132,11 @@ pub fn Todos(cx: Scope) -> impl IntoView {
|
|||
<input type="submit" value="Add"/>
|
||||
</MultiActionForm>
|
||||
<Transition fallback=move || view! {cx, <p>"Loading..."</p> }>
|
||||
{
|
||||
let delete_todo = delete_todo.clone();
|
||||
move || {
|
||||
{move || {
|
||||
let existing_todos = {
|
||||
let delete_todo = delete_todo.clone();
|
||||
move || {
|
||||
todos
|
||||
.read()
|
||||
.map({
|
||||
let delete_todo = delete_todo.clone();
|
||||
move |todos| match todos {
|
||||
todos.read()
|
||||
.map(move |todos| match todos {
|
||||
Err(e) => {
|
||||
vec![view! { cx, <pre class="error">"Server Error: " {e.to_string()}</pre>}.into_any()]
|
||||
}
|
||||
|
@ -156,29 +146,24 @@ pub fn Todos(cx: Scope) -> impl IntoView {
|
|||
} else {
|
||||
todos
|
||||
.into_iter()
|
||||
.map({
|
||||
let delete_todo = delete_todo.clone();
|
||||
move |todo| {
|
||||
let delete_todo = delete_todo.clone();
|
||||
view! {
|
||||
cx,
|
||||
<li>
|
||||
{todo.title}
|
||||
<ActionForm action=delete_todo.clone()>
|
||||
<input type="hidden" name="id" value={todo.id}/>
|
||||
<input type="submit" value="X"/>
|
||||
</ActionForm>
|
||||
</li>
|
||||
}
|
||||
.into_any()
|
||||
.map(move |todo| {
|
||||
view! {
|
||||
cx,
|
||||
<li>
|
||||
{todo.title}
|
||||
<ActionForm action=delete_todo>
|
||||
<input type="hidden" name="id" value={todo.id}/>
|
||||
<input type="submit" value="X"/>
|
||||
</ActionForm>
|
||||
</li>
|
||||
}
|
||||
.into_any()
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.unwrap_or_default()
|
||||
})
|
||||
.unwrap_or_default()
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -79,6 +79,7 @@ mod signal;
|
|||
mod signal_wrappers_read;
|
||||
mod signal_wrappers_write;
|
||||
mod spawn;
|
||||
mod stored_value;
|
||||
mod suspense;
|
||||
|
||||
pub use context::*;
|
||||
|
@ -94,6 +95,7 @@ pub use signal::*;
|
|||
pub use signal_wrappers_read::*;
|
||||
pub use signal_wrappers_write::*;
|
||||
pub use spawn::*;
|
||||
pub use stored_value::*;
|
||||
pub use suspense::*;
|
||||
|
||||
/// Trait implemented for all signal types which you can `get` a value
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
use std::rc::Rc;
|
||||
|
||||
use crate::{Memo, ReadSignal, RwSignal, Scope, UntrackedGettableSignal};
|
||||
use crate::{store_value, Memo, ReadSignal, RwSignal, Scope, StoredValue, UntrackedGettableSignal};
|
||||
|
||||
/// A wrapper for any kind of readable reactive signal: a [ReadSignal](crate::ReadSignal),
|
||||
/// [Memo](crate::Memo), [RwSignal](crate::RwSignal), or derived signal closure.
|
||||
|
@ -35,10 +33,12 @@ where
|
|||
|
||||
impl<T> Clone for Signal<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self(self.0.clone())
|
||||
Self(self.0)
|
||||
}
|
||||
}
|
||||
|
||||
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`.
|
||||
|
@ -53,7 +53,7 @@ where
|
|||
match &self.0 {
|
||||
SignalTypes::ReadSignal(s) => s.get_untracked(),
|
||||
SignalTypes::Memo(m) => m.get_untracked(),
|
||||
SignalTypes::DerivedSignal(cx, f) => cx.untrack(|| f()),
|
||||
SignalTypes::DerivedSignal(cx, f) => cx.untrack(|| f.with(|f| f())),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -64,7 +64,7 @@ where
|
|||
SignalTypes::DerivedSignal(cx, v_f) => {
|
||||
let mut o = None;
|
||||
|
||||
cx.untrack(|| o = Some(f(&v_f())));
|
||||
cx.untrack(|| o = Some(f(&v_f.with(|v_f| v_f()))));
|
||||
|
||||
o.unwrap()
|
||||
}
|
||||
|
@ -94,7 +94,10 @@ where
|
|||
/// # });
|
||||
/// ```
|
||||
pub fn derive(cx: Scope, derived_signal: impl Fn() -> T + 'static) -> Self {
|
||||
Self(SignalTypes::DerivedSignal(cx, Rc::new(derived_signal)))
|
||||
Self(SignalTypes::DerivedSignal(
|
||||
cx,
|
||||
store_value(cx, Box::new(derived_signal)),
|
||||
))
|
||||
}
|
||||
|
||||
/// Applies a function to the current value of the signal, and subscribes
|
||||
|
@ -130,7 +133,7 @@ where
|
|||
match &self.0 {
|
||||
SignalTypes::ReadSignal(s) => s.with(f),
|
||||
SignalTypes::Memo(s) => s.with(f),
|
||||
SignalTypes::DerivedSignal(_, s) => f(&s()),
|
||||
SignalTypes::DerivedSignal(_, s) => f(&s.with(|s| s())),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -163,7 +166,7 @@ where
|
|||
match &self.0 {
|
||||
SignalTypes::ReadSignal(s) => s.get(),
|
||||
SignalTypes::Memo(s) => s.get(),
|
||||
SignalTypes::DerivedSignal(_, s) => s(),
|
||||
SignalTypes::DerivedSignal(_, s) => s.with(|s| s()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -192,7 +195,7 @@ where
|
|||
{
|
||||
ReadSignal(ReadSignal<T>),
|
||||
Memo(Memo<T>),
|
||||
DerivedSignal(Scope, Rc<dyn Fn() -> T>),
|
||||
DerivedSignal(Scope, StoredValue<Box<dyn Fn() -> T>>),
|
||||
}
|
||||
|
||||
impl<T> Clone for SignalTypes<T> {
|
||||
|
@ -200,11 +203,13 @@ impl<T> Clone for SignalTypes<T> {
|
|||
match self {
|
||||
Self::ReadSignal(arg0) => Self::ReadSignal(*arg0),
|
||||
Self::Memo(arg0) => Self::Memo(*arg0),
|
||||
Self::DerivedSignal(arg0, arg1) => Self::DerivedSignal(*arg0, Rc::clone(arg1)),
|
||||
Self::DerivedSignal(arg0, arg1) => Self::DerivedSignal(*arg0, *arg1),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Copy for SignalTypes<T> {}
|
||||
|
||||
impl<T> std::fmt::Debug for SignalTypes<T>
|
||||
where
|
||||
T: std::fmt::Debug,
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
use std::rc::Rc;
|
||||
|
||||
use crate::{RwSignal, Scope, WriteSignal};
|
||||
use crate::{store_value, RwSignal, Scope, StoredValue, WriteSignal};
|
||||
|
||||
/// A wrapper for any kind of settable reactive signal: a [WriteSignal](crate::WriteSignal),
|
||||
/// [RwSignal](crate::RwSignal), or closure that receives a value and sets a signal depending
|
||||
|
@ -36,10 +34,12 @@ where
|
|||
|
||||
impl<T> Clone for SignalSetter<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self(self.0.clone())
|
||||
Self(self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Copy for SignalSetter<T> {}
|
||||
|
||||
impl<T> SignalSetter<T>
|
||||
where
|
||||
T: 'static,
|
||||
|
@ -66,7 +66,10 @@ where
|
|||
/// # });
|
||||
/// ```
|
||||
pub fn map(cx: Scope, mapped_setter: impl Fn(T) + 'static) -> Self {
|
||||
Self(SignalSetterTypes::Mapped(cx, Rc::new(mapped_setter)))
|
||||
Self(SignalSetterTypes::Mapped(
|
||||
cx,
|
||||
store_value(cx, Box::new(mapped_setter)),
|
||||
))
|
||||
}
|
||||
|
||||
/// Calls the setter function with the given value.
|
||||
|
@ -92,7 +95,7 @@ where
|
|||
pub fn set(&self, value: T) {
|
||||
match &self.0 {
|
||||
SignalSetterTypes::Write(s) => s.set(value),
|
||||
SignalSetterTypes::Mapped(_, s) => s(value),
|
||||
SignalSetterTypes::Mapped(_, s) => s.with(|s| s(value)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -114,18 +117,20 @@ where
|
|||
T: 'static,
|
||||
{
|
||||
Write(WriteSignal<T>),
|
||||
Mapped(Scope, Rc<dyn Fn(T)>),
|
||||
Mapped(Scope, StoredValue<Box<dyn Fn(T)>>),
|
||||
}
|
||||
|
||||
impl<T> Clone for SignalSetterTypes<T> {
|
||||
fn clone(&self) -> Self {
|
||||
match self {
|
||||
Self::Write(arg0) => Self::Write(*arg0),
|
||||
Self::Mapped(cx, f) => Self::Mapped(*cx, f.clone()),
|
||||
Self::Mapped(cx, f) => Self::Mapped(*cx, *f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Copy for SignalSetterTypes<T> {}
|
||||
|
||||
impl<T> std::fmt::Debug for SignalSetterTypes<T>
|
||||
where
|
||||
T: std::fmt::Debug,
|
||||
|
|
181
leptos_reactive/src/stored_value.rs
Normal file
181
leptos_reactive/src/stored_value.rs
Normal file
|
@ -0,0 +1,181 @@
|
|||
use crate::{create_rw_signal, RwSignal, Scope, UntrackedGettableSignal, UntrackedSettableSignal};
|
||||
|
||||
/// A **non-reactive** wrapper for any value, which can be created with [store_value].
|
||||
///
|
||||
/// If you want a reactive wrapper, use [create_signal](crate::create_signal).
|
||||
///
|
||||
/// This allows you to create a stable reference for any value by storing it within
|
||||
/// the reactive system. Like the signal types (e.g., [ReadSignal](crate::ReadSignal)
|
||||
/// and [RwSignal](crate::RwSignal)), it is `Copy` and `'static`. Unlike the signal
|
||||
/// types, it is not reactive; accessing it does not cause effects to subscribe, and
|
||||
/// updating it does not notify anything else.
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub struct StoredValue<T>(RwSignal<T>)
|
||||
where
|
||||
T: 'static;
|
||||
|
||||
impl<T> Clone for StoredValue<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self(self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Copy for StoredValue<T> {}
|
||||
|
||||
impl<T> StoredValue<T>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
/// Clones and returns the current stored value.
|
||||
///
|
||||
/// If you want to get the value without cloning it, use [StoredValue::with].
|
||||
/// (`value.get()` is equivalent to `value.with(T::clone)`.)
|
||||
/// ```
|
||||
/// # 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");
|
||||
/// });
|
||||
/// ```
|
||||
pub fn get(&self) -> T
|
||||
where
|
||||
T: Clone,
|
||||
{
|
||||
self.with(T::clone)
|
||||
}
|
||||
|
||||
/// Applies a function to the current stored value.
|
||||
/// ```
|
||||
/// # 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");
|
||||
/// });
|
||||
/// ```
|
||||
pub fn with<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.
|
||||
/// ```
|
||||
/// # 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");
|
||||
/// });
|
||||
/// ```
|
||||
pub fn update(&self, f: impl FnOnce(&mut T)) {
|
||||
self.0.update_untracked(f);
|
||||
}
|
||||
|
||||
/// Sets the stored value.
|
||||
/// ```
|
||||
/// # 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");
|
||||
/// });
|
||||
/// ```
|
||||
pub fn set(&self, value: T) {
|
||||
self.0.set_untracked(value);
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a **non-reactive** wrapper for any value by storing it within
|
||||
/// the reactive system.
|
||||
///
|
||||
/// Like the signal types (e.g., [ReadSignal](crate::ReadSignal)
|
||||
/// and [RwSignal](crate::RwSignal)), it is `Copy` and `'static`. Unlike the signal
|
||||
/// types, it is not reactive; accessing it does not cause effects to subscribe, and
|
||||
/// updating it does not notify anything else.
|
||||
/// ```compile_fail
|
||||
/// # use leptos_reactive::*;
|
||||
/// # create_scope(create_runtime(), |cx| {
|
||||
/// // this structure is neither `Copy` nor `Clone`
|
||||
/// pub struct MyUncloneableData {
|
||||
/// pub value: String
|
||||
/// }
|
||||
///
|
||||
/// // ❌ this won't compile, as it can't be cloned or copied into the closures
|
||||
/// let data = MyUncloneableData { value: "a".into() };
|
||||
/// let callback_a = move || data.value == "a";
|
||||
/// let callback_b = move || data.value == "b";
|
||||
/// # }).dispose();
|
||||
/// ```
|
||||
/// ```
|
||||
/// # use leptos_reactive::*;
|
||||
/// # create_scope(create_runtime(), |cx| {
|
||||
/// // this structure is neither `Copy` nor `Clone`
|
||||
/// pub struct MyUncloneableData {
|
||||
/// pub value: String
|
||||
/// }
|
||||
///
|
||||
/// // ✅ you can move the `StoredValue` and access it with .with()
|
||||
/// let data = store_value(cx, MyUncloneableData { value: "a".into() });
|
||||
/// let callback_a = move || data.with(|data| data.value == "a");
|
||||
/// let callback_b = move || data.with(|data| data.value == "b");
|
||||
/// # }).dispose();
|
||||
/// ```
|
||||
pub fn store_value<T>(cx: Scope, value: T) -> StoredValue<T>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
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()
|
||||
}
|
||||
}
|
|
@ -1,5 +1,7 @@
|
|||
use crate::{ServerFn, ServerFnError};
|
||||
use leptos_reactive::{create_rw_signal, spawn_local, ReadSignal, RwSignal, Scope};
|
||||
use leptos_reactive::{
|
||||
create_rw_signal, spawn_local, store_value, ReadSignal, RwSignal, Scope, StoredValue,
|
||||
};
|
||||
use std::{future::Future, pin::Pin, rc::Rc};
|
||||
|
||||
/// An action synchronizes an imperative `async` call to the synchronous reactive system.
|
||||
|
@ -74,8 +76,81 @@ use std::{future::Future, pin::Pin, rc::Rc};
|
|||
/// let action3 = create_action(cx, |input: &(usize, String)| async { todo!() });
|
||||
/// # });
|
||||
/// ```
|
||||
#[derive(Clone)]
|
||||
pub struct Action<I, O>
|
||||
pub struct Action<I, O>(StoredValue<ActionState<I, O>>)
|
||||
where
|
||||
I: 'static,
|
||||
O: 'static;
|
||||
|
||||
impl<I, O> Action<I, O>
|
||||
where
|
||||
I: 'static,
|
||||
O: 'static,
|
||||
{
|
||||
/// 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))
|
||||
}
|
||||
|
||||
/// 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())
|
||||
}
|
||||
|
||||
/// 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())
|
||||
}
|
||||
|
||||
/// 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| {
|
||||
state.url = if prefix.is_empty() {
|
||||
Some(T::url().to_string())
|
||||
} else {
|
||||
Some(prefix.to_string() + "/" + T::url())
|
||||
};
|
||||
});
|
||||
self
|
||||
}
|
||||
|
||||
/// How many times the action has successfully resolved.
|
||||
pub fn version(&self) -> RwSignal<usize> {
|
||||
self.0.with(|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)
|
||||
}
|
||||
|
||||
/// The most recent return value of the `async` function.
|
||||
pub fn value(&self) -> RwSignal<Option<O>> {
|
||||
self.0.with(|a| a.value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, O> Clone for Action<I, O>
|
||||
where
|
||||
I: 'static,
|
||||
O: 'static,
|
||||
{
|
||||
fn clone(&self) -> Self {
|
||||
Self(self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, O> Copy for Action<I, O>
|
||||
where
|
||||
I: 'static,
|
||||
O: 'static,
|
||||
{
|
||||
}
|
||||
|
||||
struct ActionState<I, O>
|
||||
where
|
||||
I: 'static,
|
||||
O: 'static,
|
||||
|
@ -93,7 +168,7 @@ where
|
|||
action_fn: Rc<dyn Fn(&I) -> Pin<Box<dyn Future<Output = O>>>>,
|
||||
}
|
||||
|
||||
impl<I, O> Action<I, O>
|
||||
impl<I, O> ActionState<I, O>
|
||||
where
|
||||
I: 'static,
|
||||
O: 'static,
|
||||
|
@ -115,29 +190,6 @@ where
|
|||
version.update(|n| *n += 1);
|
||||
})
|
||||
}
|
||||
|
||||
/// Whether the action has been dispatched and is currently waiting for its future to be resolved.
|
||||
pub fn pending(&self) -> ReadSignal<bool> {
|
||||
self.pending.read_only()
|
||||
}
|
||||
|
||||
/// 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<&str> {
|
||||
self.url.as_deref()
|
||||
}
|
||||
|
||||
/// 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>(mut self) -> Self {
|
||||
let prefix = T::prefix();
|
||||
self.url = if prefix.is_empty() {
|
||||
Some(T::url().to_string())
|
||||
} else {
|
||||
Some(prefix.to_string() + "/" + T::url())
|
||||
};
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates an [Action] to synchronize an imperative `async` call to the synchronous reactive system.
|
||||
|
@ -229,14 +281,17 @@ where
|
|||
Box::pin(async move { fut.await }) as Pin<Box<dyn Future<Output = O>>>
|
||||
});
|
||||
|
||||
Action {
|
||||
version,
|
||||
url: None,
|
||||
input,
|
||||
value,
|
||||
pending,
|
||||
action_fn,
|
||||
}
|
||||
Action(store_value(
|
||||
cx,
|
||||
ActionState {
|
||||
version,
|
||||
url: None,
|
||||
input,
|
||||
value,
|
||||
pending,
|
||||
action_fn,
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
/// Creates an [Action] that can be used to call a server function.
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
use crate::{ServerFn, ServerFnError};
|
||||
use leptos_reactive::{create_rw_signal, spawn_local, ReadSignal, RwSignal, Scope};
|
||||
use leptos_reactive::{
|
||||
create_rw_signal, spawn_local, store_value, ReadSignal, RwSignal, Scope, StoredValue,
|
||||
};
|
||||
use std::{future::Future, pin::Pin, rc::Rc};
|
||||
|
||||
/// An action that synchronizes multiple imperative `async` calls to the reactive system,
|
||||
|
@ -55,8 +57,78 @@ use std::{future::Future, pin::Pin, rc::Rc};
|
|||
/// let action3 = create_multi_action(cx, |input: &(usize, String)| async { todo!() });
|
||||
/// # });
|
||||
/// ```
|
||||
#[derive(Clone)]
|
||||
pub struct MultiAction<I, O>
|
||||
pub struct MultiAction<I, O>(StoredValue<MultiActionState<I, O>>)
|
||||
where
|
||||
I: 'static,
|
||||
O: 'static;
|
||||
|
||||
impl<I, O> MultiAction<I, O>
|
||||
where
|
||||
I: 'static,
|
||||
O: 'static,
|
||||
{
|
||||
}
|
||||
|
||||
impl<I, O> Clone for MultiAction<I, O>
|
||||
where
|
||||
I: 'static,
|
||||
O: 'static,
|
||||
{
|
||||
fn clone(&self) -> Self {
|
||||
Self(self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, O> Copy for MultiAction<I, O>
|
||||
where
|
||||
I: 'static,
|
||||
O: 'static,
|
||||
{
|
||||
}
|
||||
|
||||
impl<I, O> MultiAction<I, O>
|
||||
where
|
||||
I: 'static,
|
||||
O: 'static,
|
||||
{
|
||||
/// 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))
|
||||
}
|
||||
|
||||
/// The set of all submissions to this multi-action.
|
||||
pub fn submissions(&self) -> ReadSignal<Vec<Submission<I, O>>> {
|
||||
self.0.with(|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())
|
||||
}
|
||||
|
||||
/// How many times an action has successfully resolved.
|
||||
pub fn version(&self) -> RwSignal<usize> {
|
||||
self.0.with(|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| {
|
||||
a.url = if prefix.is_empty() {
|
||||
Some(T::url().to_string())
|
||||
} else {
|
||||
Some(prefix.to_string() + "/" + T::url())
|
||||
};
|
||||
});
|
||||
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
struct MultiActionState<I, O>
|
||||
where
|
||||
I: 'static,
|
||||
O: 'static,
|
||||
|
@ -115,7 +187,7 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl<I, O> MultiAction<I, O>
|
||||
impl<I, O> MultiActionState<I, O>
|
||||
where
|
||||
I: 'static,
|
||||
O: 'static,
|
||||
|
@ -156,24 +228,6 @@ where
|
|||
pub fn submissions(&self) -> ReadSignal<Vec<Submission<I, O>>> {
|
||||
self.submissions.read_only()
|
||||
}
|
||||
|
||||
/// 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<&str> {
|
||||
self.url.as_deref()
|
||||
}
|
||||
|
||||
/// 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>(mut self) -> Self {
|
||||
let prefix = T::prefix();
|
||||
self.url = if prefix.is_empty() {
|
||||
Some(T::url().to_string())
|
||||
} else {
|
||||
Some(prefix.to_string() + "/" + T::url())
|
||||
};
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates an [MultiAction] to synchronize an imperative `async` call to the synchronous reactive system.
|
||||
|
@ -240,13 +294,16 @@ where
|
|||
Box::pin(async move { fut.await }) as Pin<Box<dyn Future<Output = O>>>
|
||||
});
|
||||
|
||||
MultiAction {
|
||||
MultiAction(store_value(
|
||||
cx,
|
||||
version,
|
||||
submissions,
|
||||
url: None,
|
||||
action_fn,
|
||||
}
|
||||
MultiActionState {
|
||||
cx,
|
||||
version,
|
||||
submissions,
|
||||
url: None,
|
||||
action_fn,
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
/// Creates an [MultiAction] that can be used to call a server function.
|
||||
|
|
|
@ -144,11 +144,11 @@ where
|
|||
url
|
||||
} else {
|
||||
debug_warn!("<ActionForm/> action needs a URL. Either use create_server_action() or Action::using_server_fn().");
|
||||
""
|
||||
}.to_string();
|
||||
let version = action.version;
|
||||
let value = action.value;
|
||||
let input = action.input;
|
||||
String::new()
|
||||
};
|
||||
let version = action.version();
|
||||
let value = action.value();
|
||||
let input = action.input();
|
||||
|
||||
let on_form_data = Rc::new(move |form_data: &web_sys::FormData| {
|
||||
let data = action_input_from_form_data(form_data);
|
||||
|
@ -219,8 +219,8 @@ where
|
|||
url
|
||||
} else {
|
||||
debug_warn!("<MultiActionForm/> action needs a URL. Either use create_server_action() or Action::using_server_fn().");
|
||||
""
|
||||
}.to_string();
|
||||
String::new()
|
||||
};
|
||||
|
||||
let on_submit = move |ev: web_sys::SubmitEvent| {
|
||||
if ev.default_prevented() {
|
||||
|
|
Loading…
Reference in a new issue