use_memo

use_memo is a hook to cache the result of a calculation, function, or operation. This is useful when you have a value that is expensive to compute and you want to avoid re-computing it on every render. The value is computed once and then stored in the memoized value. The memoized value is returned on subsequent renders until the dependencies change.

Example

from deephaven import ui


@ui.component
def ui_todo_list(todos: list[str], filter: str):
    filtered_todos = ui.use_memo(
        lambda: [todo for todo in todos if filter in todo], [todos, filter]
    )

    return [
        ui.text(f"Showing {len(filtered_todos)} of {len(todos)} todos"),
        *[ui.checkbox(todo) for todo in filtered_todos],
    ]


result = ui_todo_list(["Do grocery shopping", "Walk the dog", "Do laundry"], "Do")

In the example above, the filtered_todos value is computed once and then stored in the memoized value. The memoized value is returned on subsequent renders until the todos or filter dependencies change.

Recommendations

Recommendations for memoizing values:

  1. Use memoization for expensive computations: If you have a value that is expensive to compute, use use_memo to memoize the value between renders.
  2. Use dependencies: Pass in only the dependencies that the memoized value relies on. If any of the dependencies change, the memoized value is re-computed. If another prop or state value changes, the computation is not re-run.
  3. Do not use for cheap computations: There is a small overhead to memoizing values, so only use use_memo for expensive computations. If the value is cheap to compute, it is not worth memoizing.
  4. Memoize table/plot operations: If you are working with a table, memoize the table or plot operation, storing the result in a memoized value. This will prevent the table or plot from being re-computed on every render.

Optimizing performance

In the example below, we have a list of todos, a filter string, and a “theme” that can be set. First we demonstrate how slow it is to compute the filtered todos without memoization.

from deephaven import ui
import time


# theme colors
theme_options = ["accent-200", "red-200", "green-200"]


@ui.component
def ui_todo_list(todos: list[str], search: str, theme: str):
    def filter_todos():
        # Simulate delay
        time.sleep(2)
        return [todo for todo in todos if search in todo]

    filtered_todos = filter_todos()

    return ui.view(
        ui.flex(
            ui.text(f"Showing {len(filtered_todos)} todos"),
            *[ui.checkbox(todo) for todo in filtered_todos],
            direction="column",
        ),
        padding="size-200",
        background_color=theme,
    )


@ui.component
def ui_todo_app(todos: list[str]):
    search, set_search = ui.use_state("")
    theme, set_theme = ui.use_state(theme_options[0])

    return [
        ui.text_field(value=search, label="Search", on_change=set_search),
        ui.picker(
            *theme_options, label="Theme", selected_key=theme, on_change=set_theme
        ),
        ui_todo_list(todos, search, theme),
    ]


todo_app = ui_todo_app(["Do grocery shopping", "Walk the dog", "Do laundry"])

We are computing the filtered todos on every render, which is slow. We even call it when theme is changed, even though that value is not used in the computation. We can optimize this by using use_memo to memoize the filtered todos and only recompute them when one of the dependent values (todos and search) is updated:

from deephaven import ui
import time


theme_options = ["accent-200", "red-200", "green-200"]


@ui.component
def ui_todo_list(todos: list[str], search: str, theme: str):
    def filter_todos():
        # Simulate delay based on filter length
        time.sleep(min(len(search) + 1, 3))
        return [todo for todo in todos if search in todo]

    filtered_todos = ui.use_memo(filter_todos, [todos, search])

    return ui.view(
        ui.flex(
            ui.text(f"Showing {len(filtered_todos)} todos"),
            *[ui.checkbox(todo) for todo in filtered_todos],
            direction="column",
        ),
        background_color=theme,
    )


@ui.component
def ui_todo_app(todos: list[str]):
    search, set_search = ui.use_state("")
    theme, set_theme = ui.use_state(theme_options[0])

    return [
        ui.text_field(value=search, label="Search", on_change=set_search),
        ui.picker(
            *theme_options, label="Theme", selected_key=theme, on_change=set_theme
        ),
        ui_todo_list(todos, search, theme),
    ]


todo_app = ui_todo_app(["Do grocery shopping", "Walk the dog", "Do laundry"])

Now, switching the theme will always be snappy, and the filtered todos will only be recomputed when the todos or search dependencies change.

Memoize table operations

use_memo can be used to memoize table operations. In the example below, we have a slider to adjust an integer value, a ticking table that will add a new row every second with the new result, and our theme picker from the previous example:

from deephaven import time_table, ui
from deephaven.table import Table


theme_options = ["accent-200", "red-200", "green-200"]


@ui.component
def ui_memo_table_app():
    n, set_n = ui.use_state(1)
    theme, set_theme = ui.use_state(theme_options[0])

    return ui.view(
        ui.flex(
            ui.picker(
                *theme_options, label="Theme", selected_key=theme, on_change=set_theme
            ),
            ui.slider(value=n, min_value=1, max_value=999, on_change=set_n, label="n"),
            # ❌ This table will be re-created on every render
            time_table("PT1s").update(f"x=i*{n}").reverse(),
            direction="column",
            height="100%",
        ),
        background_color=theme,
        align_self="stretch",
        flex_grow=1,
    )


memo_table_app = ui_memo_table_app()

However, you’ll notice that if you change the theme, the table will be re-created and start again with no rows. We only want to re-create the table when n changes and we actually need to recreate the table. In that case, we can memoize the table operation:

from deephaven import time_table, ui
from deephaven.table import Table


theme_options = ["accent-200", "red-200", "green-200"]


@ui.component
def ui_memo_table_app():
    n, set_n = ui.use_state(1)
    theme, set_theme = ui.use_state(theme_options[0])

    # ✅ Memoize the table operation, only recompute when the dependency `n` changes
    result_table = ui.use_memo(
        lambda: time_table("PT1s").update(f"x=i*{n}").reverse(), [n]
    )

    return ui.view(
        ui.flex(
            ui.picker(
                *theme_options, label="Theme", selected_key=theme, on_change=set_theme
            ),
            ui.slider(value=n, min_value=1, max_value=999, on_change=set_n, label="n"),
            result_table,
            direction="column",
            height="100%",
        ),
        background_color=theme,
        align_self="stretch",
        flex_grow=1,
    )


memo_table_app = ui_memo_table_app()

Memoize each item in a list

use_memo is a hook, and like any other hook, can only be called at the top level of a component. Suppose you are listing several items, and you want them to be memoized individually. However, you can’t use use_memo, as an error is thrown if the count of hooks changes between renders:

from deephaven import ui
import time


theme_options = ["accent-200", "red-200", "green-200"]


def fib(n):
    if n <= 1:
        return n
    time.sleep(1)
    return fib(n - 1) + fib(n - 2)


@ui.component
def ui_fibonacci_list(n: int, theme: str):

    return ui.view(
        # ❌ You can't call `use_memo` in a loop like this
        *[ui.view(f"{i}: {ui.use_memo(lambda: fib(i), [i])}") for i in range(n)],
        background_color=theme,
    )


@ui.component
def ui_fibonacci_app():
    n, set_n = ui.use_state(1)
    theme, set_theme = ui.use_state(theme_options[0])

    return [
        ui.slider(value=n, min_value=1, max_value=5, on_change=set_n, label="n"),
        ui.picker(
            *theme_options, label="Theme", selected_key=theme, on_change=set_theme
        ),
        ui_fibonacci_list(n, theme),
    ]


fibonacci_app = ui_fibonacci_app()

Instead, extract each item into its own component and memoize the value there:

from deephaven import ui
import time


theme_options = ["accent-200", "red-200", "green-200"]


def fib(n):
    if n <= 1:
        return n
    time.sleep(1)
    return fib(n - 1) + fib(n - 2)


@ui.component
def ui_fibonacci_item(i: int):
    # ✅ Call `use_memo` at the top level of the component
    value = ui.use_memo(lambda: fib(i), [i])
    return ui.view(f"{i}: {value}")


@ui.component
def ui_fibonacci_list(n: int, theme: str):
    return ui.view(
        *[ui_fibonacci_item(i) for i in range(n)],
        background_color=theme,
        padding="size-100",
    )


@ui.component
def ui_fibonacci_app():
    n, set_n = ui.use_state(1)
    theme, set_theme = ui.use_state(theme_options[0])

    return [
        ui.slider(value=n, min_value=1, max_value=5, on_change=set_n, label="n"),
        ui.picker(
            *theme_options, label="Theme", selected_key=theme, on_change=set_theme
        ),
        ui_fibonacci_list(n, theme),
    ]


fibonacci_app = ui_fibonacci_app()

API Reference

Memoize the result of a function call. The function will only be called again if the dependencies change.

Returns: TypeVar(T) The memoized result of the function call.

ParametersTypeDefaultDescription
funcCallable[[], TypeVar(T)]The function to memoize.
dependenciesTuple[Any] |
List[Any]
The dependencies to check for changes.