Skip to content
4 changes: 2 additions & 2 deletions docs/app/reflex_docs/templates/docpage/docpage.py
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ def feedback_button_toc() -> rx.Component:


@rx.memo
def copy_to_markdown(text: str) -> rx.Component:
def copy_to_markdown(text: rx.Var[str]) -> rx.Component:
copied = ClientStateVar.create("is_copied", default=False, global_ref=False)
return marketing_button(
rx.cond(
Expand Down Expand Up @@ -270,7 +270,7 @@ def link_pill(text: str, href: str) -> rx.Component:


@rx.memo
def docpage_footer(path: str):
def docpage_footer(path: rx.Var[str]) -> rx.Component:
from reflex_site_shared.constants import FORUM_URL, ROADMAP_URL

return rx.el.footer(
Expand Down
4 changes: 2 additions & 2 deletions docs/enterprise/drag-and-drop.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class BasicDndState(rx.State):


@rx.memo
def draggable_card():
def draggable_card() -> rx.Component:
return rxe.dnd.draggable(
rx.card(
rx.text("Drag me!", weight="bold"),
Expand Down Expand Up @@ -274,7 +274,7 @@ class DynamicListState(rx.State):


@rx.memo
def draggable_list_item(item: ListItem):
def draggable_list_item(item: rx.Var[ListItem]) -> rx.Component:
return rxe.dnd.draggable(
rx.card(
rx.text(item.text, weight="bold"),
Expand Down
4 changes: 3 additions & 1 deletion docs/enterprise/react_flow/nodes.md
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,9 @@ class CustomNodeState(rx.State):


@rx.memo
def color_selector_node(data: rx.Var[dict], isConnectable: rx.Var[bool]):
def color_selector_node(
data: rx.Var[dict], isConnectable: rx.Var[bool]
) -> rx.Component:
data = data.to(dict)
return rx.el.div(
rxe.flow.handle(
Expand Down
2 changes: 1 addition & 1 deletion docs/library/data-display/icon.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ icon_search_cs = ClientStateVar.create("icon_search", default="")


@rx.memo
def lucide_icons():
def lucide_icons() -> rx.Component:
return rx.box(
rx.box(
rx.box(
Expand Down
187 changes: 93 additions & 94 deletions docs/library/other/memo.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,22 @@ import reflex as rx

# Memo

The `memo` decorator is used to optimize component rendering by memoizing components that don't need to be re-rendered. This is particularly useful for expensive components that depend on specific props and don't need to be re-rendered when other state changes in your application.
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

When using `rx.memo`, you must follow these 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. **Type all arguments**: All arguments to a memoized component must have type annotations.
2. **Use keyword arguments**: When calling a memoized component, you must use keyword arguments (not positional arguments).
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.

## Basic Usage
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.

When you wrap a component function with `@rx.memo`, the component will only re-render when its props change. This helps improve performance by preventing unnecessary re-renders.
## Basic Usage

```python
# Define a state class to track count
class DemoState(rx.State):
count: int = 0

Expand All @@ -27,150 +28,148 @@ class DemoState(rx.State):
self.count += 1


# Define a memoized component
@rx.memo
def expensive_component(label: str) -> rx.Component:
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.text("This component only re-renders when props change."),
rx.divider(),
)


# Use the memoized component in your app
def index():
return rx.vstack(
rx.heading("Memo Example"),
rx.text("Count: 0"), # This will update with state.count
rx.text(f"Count: {DemoState.count}"),
rx.button("Increment", on_click=DemoState.increment),
rx.divider(),
expensive_component(label="Memoized Component"), # Must use keyword arguments
spacing="4",
padding="4",
border_radius="md",
border="1px solid #eaeaea",
expensive_component(label="Memoized Component"),
)
```

In this example, the `expensive_component` will only re-render when the `label` prop changes, not when the `count` state changes.
`expensive_component` re-renders only when `label` changes — bumping `DemoState.count` does not invalidate it.

## With Event Handlers
## With State Variables

You can also use `rx.memo` with components that have event handlers:
Props can be ordinary Vars. The memoized component re-renders when those Vars change:

```python
# Define a state class to track clicks
class ButtonState(rx.State):
clicks: int = 0

@rx.event
def increment(self):
self.clicks += 1
class AppState(rx.State):
name: str = "World"


# Define a memoized button component
@rx.memo
def my_button(text: str, on_click: rx.EventHandler) -> rx.Component:
return rx.button(text, on_click=on_click)
def greeting(name: rx.Var[str]) -> rx.Component:
return rx.heading("Hello, " + name)


# Use the memoized button in your app
def index():
return rx.vstack(
rx.text("Clicks: 0"), # This will update with state.clicks
my_button(text="Click me", on_click=ButtonState.increment),
spacing="4",
greeting(name=AppState.name),
rx.input(value=AppState.name, on_change=AppState.set_name),
)
```

## With State Variables
## Forwarding Props with `rx.RestProp`

When used with state variables, memoized components will only re-render when the specific state variables they depend on change:
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
# Define a state class with multiple variables
class AppState(rx.State):
name: str = "World"
count: int = 0
@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)

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

@rx.event
def set_name(self, name: str):
self.name = name
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.

# Define a memoized greeting component
## Accepting Children

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

```python
@rx.memo
def greeting(name: str) -> rx.Component:
return rx.heading("Hello, " + name) # Will display the name prop
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",
)


# Use the memoized component with state variables
def index():
return rx.vstack(
greeting(name=AppState.name), # Must use keyword arguments
rx.text("Count: 0"), # Will display the count
rx.button("Increment Count", on_click=AppState.increment),
rx.input(
placeholder="Enter your name",
on_change=AppState.set_name,
value="World", # Will be bound to AppState.name
),
spacing="4",
return card(
rx.text("Body copy goes here."),
title="Memoized card",
)
```

## Advanced Event Handler Example
## Returning a `Var` Instead of a Component

You can also pass arguments to event handlers in memoized components:
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
# Define a state class to track messages
class MessageState(rx.State):
message: str = ""

@rx.event
def set_message(self, text: str):
self.message = text
class PriceState(rx.State):
amount: int = 100
currency: str = "USD"


# Define a memoized component with event handlers that pass arguments
@rx.memo
def action_buttons(
on_action: rx.EventHandler[rx.event.passthrough_event_spec(str)],
) -> rx.Component:
return rx.hstack(
rx.button("Save", on_click=on_action("Saved!")),
rx.button("Delete", on_click=on_action("Deleted!")),
rx.button("Cancel", on_click=on_action("Cancelled!")),
spacing="2",
)
def format_price(amount: rx.Var[int], currency: rx.Var[str]) -> rx.Var[str]:
return currency.to(str) + ": $" + amount.to(str)


# Use the memoized component with event handlers
def index():
formatted = format_price(amount=PriceState.amount, currency=PriceState.currency)
return rx.vstack(
rx.text("Status: "), # Will display the message
action_buttons(on_action=MessageState.set_message),
spacing="4",
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

Use `rx.memo` for:
Reach for `rx.memo` when:

- Components with expensive rendering logic
- Components that render the same result given the same props
- Components that re-render too often due to parent component updates
- 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.

Avoid using `rx.memo` for:
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: ...
```

- Simple components where the memoization overhead might exceed the performance gain
- Components that almost always receive different props on re-render
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

Expand All @@ -180,8 +179,8 @@ Avoid using `rx.memo` for:
rx.memo(component_fn)
```

Decorates a function that returns a Reflex component so it can be reused as a memoized component. The function arguments must be type annotated, and memoized components should be called with keyword arguments.
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]` | Function that returns the component to memoize. |
| `component_fn` | `Callable[..., rx.Component \| rx.Var]` | The function to memoize. All parameters must be `rx.Var[...]` or `rx.RestProp`. |
Loading
Loading