Merge pull request #203 from gbj/stored-value

`store_value`
This commit is contained in:
Greg Johnston 2022-12-30 19:10:25 -05:00 committed by GitHub
commit 7ad94cc520
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 433 additions and 165 deletions

View file

@ -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"/>

View file

@ -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()
}
};

View file

@ -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()
}
};

View file

@ -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

View file

@ -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,

View file

@ -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,

View 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()
}
}

View file

@ -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.

View file

@ -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.

View file

@ -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() {