Files
dioxus/packages/signals/docs/memo.md
T
Evan AlmloffandJonathan Kelley 58c41f8561 Stores: Externally tracked state and reactive collections (#4483)
* map signals mutably

* boxed signals

* remove mutable borrow type from boxed writable

* improve map mut example

* restore boxed_mut helper

* make boxed readable/writable types copy

* Move away from the ReadOnlySignal alias in examples

* automatically convert Write

* Fix some tests

* ReadSignal and WriteSignal

* move read only signal alias

* make maps copy

* store prototype

* implement derive macro

* Handle generics in the derive macro

* Make Selector boxable

* derive boxed conversion

* Get rid of store type

* Remove hardcoded type from derive macro

* selector -> store

* Refactor store implementation

* more vec methods

* todo mvc store example

* Implement Storable for HashMap

* fix write

* restore sorting logic in todomvc

* derive readable and writable

* improve todomvc store example

* Result store

* Option store

* collect dead subscribers

* fix clippy and formatting

* recognize some common foreign types automatically

* fix foreign stores in props

* slice and array selectors

* clean up store example

* writable is child owned

* box selector

* read and write to builtins

* re-use the proc macro in the derive macro

* Switch to a extension trait based approach

* switch to extension traits in the derive macro

* transposed method

* derive enums

* Expose Store::new

* remove stores from the prelude

* More consistent naming scheme for SelectorScope methods

* document hashmap methods

* document index extension

* document option ext

* document result ext

* document slice extension

* document the vec extension

* make more methods borrow the store mutably

* Move to impls to have higher priority over the builtin write extension traits

* fix deadlock

* finish stores documentation

* Document the store macro

* cleanup docs and organization

* finish derive docs

* fix typo

* fix clippy

* fix doc tests

* fix derive macro with generics

* fix clippy

* test and fix empty structs

* clean up todomvc store example

* default to a boxed store writer to make it easier to type when providing to the context api

* fix cargo doc

* fix clippy in store example

* relax generational box lifetime bounds

* relax bounds

* relax signal bounds

* fix compilation

* read store

* relax bounds on default store impls

* fix enum derive

* Convert to readsignal and writesignal automatically

* simplify type matching logic

* add write store

* unsized stores

* shrink the path vec type

* use more concrete types

* remove opaque types

* remove copy bounds

* use lens as generic name everywhere

* more consistent borrow semantics

* use a concrete type for the signal metadata and mention references in the map docs

* improve todomvc store docs

* document store derive macro internals

* refactor macro

* improve selector node documentation

* fix clippy

* add btreemap

* fix doc tests

* more option methods

* fix doc

* fix clippy

* add a bunch more result methods

* add a bunch of bounds to the iterators

* fix clippy

* remove option from subscribers

* add store macro to generate extension traits

* fix store example

* fix deadlock

* fix docs

* fix doc tests

* fix clippy

* add stores to public api

* add store macro, and then update todomvc_store example

---------

Co-authored-by: Jonathan Kelley <jkelleyrtp@gmail.com>
2025-08-05 11:30:21 -07:00

4.5 KiB

Memos are the result of computing a value from use_memo.

You may have noticed that this struct doesn't have many methods. Most methods for Memo are defined on the [ReadableExt] trait.

Reading a Memo

You can use the methods on the ReadableExt trait to read a memo:

# use dioxus::prelude::*;

fn app() -> Element {
    let mut count = use_signal(|| 0);
    // The memo will rerun any time we write to the count signal
    let halved = use_memo(move || count() / 2);

    rsx! {
        // When we read the value of memo, the current component will subscribe to the result of the memo. It will only rerun when the result of the memo changes.
        "{halved}"
        button {
            onclick: move |_| {
                count += 1;
            },
            "Increment"
        }
    }
}

Memo also includes helper methods just like [Signal]s to make it easier to use. Calling a memo like a function will clone the inner value:

# use dioxus::prelude::*;

fn app() -> Element {
    let mut count = use_signal(|| 0);
    // The memo will rerun any time we write to the count signal
    let halved = use_memo(move || count() / 2);
    // This will rerun any time the halved value changes
    let doubled = use_memo(move || 2 * halved());

    rsx! {
        "{doubled}"
        button {
            onclick: move |_| {
                count += 1;
            },
            "Increment"
        }
    }
}

For a full list of all the helpers available, check out the [Readable], ReadableVecExt, and ReadableOptionExt traits.

Memos with Async

Because Memos check borrows at runtime, you need to be careful when reading memos inside of async code. If you hold a read of a memo over an await point, that read may still be open when the memo reruns which will cause a panic:

# use dioxus::prelude::*;
# async fn sleep(delay: u32) {}
async fn double_me_async(value: &u32) -> u32 {
    sleep(100).await;
    *value * 2
}
let mut signal = use_signal(|| 0);
let halved = use_memo(move || signal() / 2);

let doubled = use_resource(move || async move {
    // Don't hold reads over await points
    let halved = halved.read();
    // While the future is waiting for the async work to finish, the read will be open
    double_me_async(&halved).await
});

rsx!{
    "{doubled:?}"
    button {
        onclick: move |_| {
            // When you write to signal, it will cause the memo to rerun which may panic because you are holding a read of the memo over an await point
            signal += 1;
        },
        "Increment"
    }
};

Instead of holding a read over an await point, you can clone whatever values you need out of your memo:

# use dioxus::prelude::*;
# async fn sleep(delay: u32) {}
async fn double_me_async(value: u32) -> u32 {
    sleep(100).await;
    value * 2
}
let mut signal = use_signal(|| 0);
let halved = use_memo(move || signal() / 2);

let doubled = use_resource(move || async move {
    // Calling the memo will clone the inner value
    let halved = halved();
    double_me_async(halved).await;
});

rsx!{
    "{doubled:?}"
    button {
        onclick: move |_| {
            signal += 1;
        },
        "Increment"
    }
};

Memo lifecycle

Memos are implemented with generational-box which makes all values Copy even if the inner value is not Copy.

This is incredibly convenient for UI development, but it does come with some tradeoffs. The lifetime of the memo is tied to the lifetime of the component it was created in. If you drop the component that created the memo, the memo will be dropped as well. You might run into this if you try to pass a memo from a child component to a parent component and drop the child component. To avoid this you can create your memo higher up in your component tree, or use global memos.

TLDR Don't pass memos up in the component tree. It will cause issues:

# use dioxus::prelude::*;
fn MyComponent() -> Element {
    let child_signal = use_signal(|| None);

    rsx! {
        IncrementButton {
            child_signal
        }
    }
}

#[component]
fn IncrementButton(mut child_signal: Signal<Option<Memo<i32>>>) -> Element {
    let signal_owned_by_child = use_signal(|| 0);
    let memo_owned_by_child = use_memo(move || signal_owned_by_child() * 2);
    // Don't do this: it may cause issues if you drop the child component
    child_signal.set(Some(memo_owned_by_child));

    todo!()
}