import reflex as rxThe @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.
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.
rx.Var[T]for props β annotate each prop asrx.Var[T]whereTis the prop's runtime type (str,int, a TypedDict, etc.). Inside the function body, the parameter is aVaryou compose into the rendered tree.rx.RestPropfor spread props β at most one parameter may be annotated asrx.RestProp, which forwards unrecognized kwargs through to the rendered root.rx.Var[rx.Component]for slot children β a parameter namedchildrenannotated asrx.Var[rx.Component]accepts children rendered by the caller.- 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.
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.
Props can be ordinary Vars. The memoized component re-renders when those Vars change:
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),
)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.
@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.
Declare a parameter named children typed as rx.Var[rx.Component] to receive a child subtree.
@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",
)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.
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.
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.
The previous rx.memo accepted plain-typed arguments (def card(title: str)). The new one requires rx.Var[...]. To migrate:
# 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.
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]).
| Argument | Type | Description |
|---|---|---|
component_fn |
Callable[..., rx.Component | rx.Var] |
The function to memoize. All parameters must be rx.Var[...] or rx.RestProp. |