Back to Reflex

Memo

docs/library/other/memo.md

0.9.4a15.5 KB
Original Source
python
import reflex as rx

Memo

The @rx.memo decorator turns a function into a memoized React component. The compiler emits the function as its own module, and React's memo only re-renders it when its declared props change. Reach for it when a subtree is expensive to render and depends on a narrow slice of state.

Requirements

Every parameter must be annotated with rx.Var[...] or rx.RestProp. The compiler reads those annotations to generate prop names, prop forwarding, and the JS function signature.

  1. rx.Var[T] for props — annotate each prop as rx.Var[T] where T is the prop's runtime type (str, int, a TypedDict, etc.). Inside the function body, the parameter is a Var you compose into the rendered tree.
  2. rx.RestProp for spread props — at most one parameter may be annotated as rx.RestProp, which forwards unrecognized kwargs through to the rendered root.
  3. rx.Var[rx.Component] for slot children — a parameter named children annotated as rx.Var[rx.Component] accepts children rendered by the caller.
  4. Keyword arguments at the call site — pass props by name, not by position.

Defaults need to be rx.Var values. For the common empty cases use the module-level constants rx.EMPTY_VAR_STR (an empty string) and rx.EMPTY_VAR_INT (zero): class_name: rx.Var[str] = rx.EMPTY_VAR_STR falls back to "" when the caller omits the prop.

Basic Usage

python
class DemoState(rx.State):
    count: int = 0

    @rx.event
    def increment(self):
        self.count += 1


@rx.memo
def expensive_component(label: rx.Var[str]) -> rx.Component:
    return rx.vstack(
        rx.heading(label),
        rx.text("This component only re-renders when props change."),
        rx.divider(),
    )


def index():
    return rx.vstack(
        rx.text(f"Count: {DemoState.count}"),
        rx.button("Increment", on_click=DemoState.increment),
        expensive_component(label="Memoized Component"),
    )

expensive_component re-renders only when label changes — bumping DemoState.count does not invalidate it.

With State Variables

Props can be ordinary Vars. The memoized component re-renders when those Vars change:

python
class AppState(rx.State):
    name: str = "World"


@rx.memo
def greeting(name: rx.Var[str]) -> rx.Component:
    return rx.heading("Hello, " + name)


def index():
    return rx.vstack(
        greeting(name=AppState.name),
        rx.input(value=AppState.name, on_change=AppState.set_name),
    )

Forwarding Props with rx.RestProp

Use rx.RestProp to accept and forward arbitrary props (think ...rest in JSX). Useful for thin wrappers that re-style a primitive without redeclaring every prop.

python
@rx.memo
def primary_button(
    rest: rx.RestProp,
    *,
    label: rx.Var[str],
) -> rx.Component:
    return rx.button(label, class_name="bg-primary-9 text-white", **rest)


def index():
    return primary_button(
        label="Save",
        on_click=rx.console_log("clicked"),
        id="save",
    )

At most one rx.RestProp parameter is allowed per memo.

Accepting Children

Declare a parameter named children typed as rx.Var[rx.Component] to receive a child subtree.

python
@rx.memo
def card(
    children: rx.Var[rx.Component],
    *,
    title: rx.Var[str],
) -> rx.Component:
    return rx.box(
        rx.heading(title),
        children,
        class_name="border border-slate-5 rounded-lg p-4",
    )


def index():
    return card(
        rx.text("Body copy goes here."),
        title="Memoized card",
    )

Returning a Var Instead of a Component

A memo function can return rx.Var[T] instead of rx.Component. The compiler emits a plain JavaScript function and the call site is just a Var you can compose into the page.

python
class PriceState(rx.State):
    amount: int = 100
    currency: str = "USD"


@rx.memo
def format_price(amount: rx.Var[int], currency: rx.Var[str]) -> rx.Var[str]:
    return currency.to(str) + ": $" + amount.to(str)


def index():
    formatted = format_price(amount=PriceState.amount, currency=PriceState.currency)
    return rx.vstack(
        rx.text(formatted),
    )

The body of a Var-returning memo runs at compile time and is restricted to Var operations — no hooks, no Python branching on the Vars.

Performance Considerations

Reach for rx.memo when:

  • The component is expensive to render.
  • Its output is a stable function of a small set of props.
  • A frequently-updating ancestor would otherwise force it to re-render.

Skip it when:

  • The component is cheap and the bookkeeping is not worth it.
  • The props change on every render anyway — memo never gets to short-circuit.

Migrating from the Old rx.memo

The previous rx.memo accepted plain-typed arguments (def card(title: str)). The new one requires rx.Var[...]. To migrate:

python
# Before
@rx.memo
def card(title: str) -> rx.Component: ...


# After
@rx.memo
def card(title: rx.Var[str]) -> rx.Component: ...

The old rx._x.memo alias still resolves to the new memo and prints a one-time was promoted to rx.memo notice.

API Reference

rx.memo

python
rx.memo(component_fn)

Wraps a function whose parameters are all rx.Var[...] or rx.RestProp. Returns a callable that constructs the memoized component (or a Var if the function's return annotation is rx.Var[T]).

ArgumentTypeDescription
component_fnCallable[..., rx.Component | rx.Var]The function to memoize. All parameters must be rx.Var[...] or rx.RestProp.