leptos icon indicating copy to clipboard operation
leptos copied to clipboard

Add new_local to Memo

Open tage64 opened this issue 11 months ago • 3 comments

Dear Leptos Developers,

I don't find a new_local() method on Memo. Is this intentional or could this be easily fixed?

Thanks, Tage

tage64 avatar May 24 '25 10:05 tage64

Hi @tage64 , There's unfortunately some implementation details that have prevented this from existing, but perhaps it can be investigated further @gbj

benwis avatar May 24 '25 10:05 benwis

Is there an other way to create a Memo<T, LocalStorage>?

flosse avatar Jun 06 '25 11:06 flosse

Yes, there are indeed internal reasons that it isn't directly supported, i.e., that you can't do this:

    let some_thread_unsafe_value = Rc::new(42);
    let some_memo = Memo::new(move |_| *some_thread_unsafe_value * 2);

However, there are a couple possibilities that are close to trivial to use.

1. If the issue is that you are capturing a thread-unsafe value in the memo function, you can use a SendWrapper to wrap the value:

    let some_thread_unsafe_value = Rc::new(42);
    let wrapped = SendWrapper::new(some_thread_unsafe_value);
    let some_memo = Memo::new(move |_| **wrapped * 2);

Note that if you do this during SSR, you are very likely to create footguns, as it will panic if the wrapped value ever actually moves between threads (for example, if you are using Axum and you use the memo somewhere inside a Suspense). (With CSR, it is perfectly safe and fine.)

2. If the issue is that you are returning a thread-unsafe type, you can also use SendWrapper (with the same caveats about SSR), although you'll need to create a newtype so you can implement PartialEq, which SendWrapper doesn't do:

#[component]
pub fn App() -> impl IntoView {
    let user_input = RwSignal::new(42i32);
    let some_memo = Memo::new(move |_| {
        let value = user_input.get();
        MyWrapper(SendWrapper::new(Rc::new(value)))
    });
}

struct MyWrapper(SendWrapper<Rc<i32>>);

impl PartialEq for MyWrapper {
    fn eq(&self, other: &Self) -> bool {
        **self.0 == **other.0
    }
}

3. In either case, if the problem is the classic "this value only exists in the browser and is None on the server, but JsValue is always !Send" then you can/should use the SendOption type as a wrapper instead. This one will always be safe for SSR.

    let user_input = RwSignal::new(None::<i32>);
    let some_memo = Memo::new(move |_| {
        // imagine this is some browser type instead of Rc
        let value = user_input.get().map(|input| Rc::new(input * 2));
        SendOption::new_local(value)
    });

gbj avatar Jun 06 '25 18:06 gbj