Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions docs/api-reference/browser_storage.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,15 @@ class CookieState(rx.State):
c6: str = rx.Cookie(name="c6-custom-name")
```

```md alert info
# The default value is the first positional argument.

`rx.Cookie`, `rx.LocalStorage`, and `rx.SessionStorage` all take the default
value as their first positional argument, e.g. `rx.LocalStorage("light")`. It
cannot be passed as a keyword argument — the keyword arguments (`name`,
`max_age`, `sync`, etc.) only configure the storage behavior.
```

```md alert warning
# **The default value of a Cookie is never set in the browser!**

Expand Down
57 changes: 57 additions & 0 deletions docs/api-reference/event_triggers.md
Original file line number Diff line number Diff line change
Expand Up @@ -440,3 +440,60 @@ def scroll_example():
width="100%",
)
```

## on_key_down

The on_key_down event handler is called when the user presses a key while the element has focus. The handler receives the name of the key (e.g. `"Enter"`, `"Escape"`, `"a"`, `"ArrowUp"`) and a dictionary describing the active modifier keys (`alt_key`, `ctrl_key`, `meta_key`, `shift_key`).

A handler may accept only the key name if it does not need the modifier info.

```python demo exec
class KeyDownState(rx.State):
message: str = ""

@rx.event
def handle_key_down(self, key: str):
if key == "Enter":
self.message = "You pressed Enter!"
else:
self.message = f"Last key pressed: {key}"


def key_down_example():
return rx.vstack(
rx.text(KeyDownState.message),
rx.input(
placeholder="Focus me and press a key...",
on_key_down=KeyDownState.handle_key_down,
),
)
```

## on_key_up

The on_key_up event handler is called when the user releases a key. It receives the same arguments as `on_key_down`.

## Global Keyboard Events

`on_key_down` on an element only fires while that element has focus. To listen for keyboard events anywhere on the page — for example, to implement global hotkeys — attach `on_key_down` to the `rx.window_event_listener` component, which listens on the browser window and renders no visible output.

```python
class HotkeyState(rx.State):
last_key_pressed: str = ""

@rx.event
def on_hotkey_press(self, key: str):
if key not in ("w", "a", "s", "d"):
return
self.last_key_pressed = key


def hotkey_example():
return rx.vstack(
rx.text("Press w, a, s, or d anywhere on the page."),
rx.text(f"Last hotkey pressed: {HotkeyState.last_key_pressed}"),
rx.window_event_listener(
on_key_down=HotkeyState.on_hotkey_press,
),
)
```
48 changes: 48 additions & 0 deletions docs/api-reference/special_events.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ rx.button("Scroll to download button", on_click=rx.scroll_to("download button"))

When this is triggered, it scrolls to an element passed by id as parameter. Click on button to scroll to download button (rx.download section) at the bottom of the page

To keep a container automatically scrolled to the bottom as new content is added — for example a chat or log view — use the [`rx.auto_scroll`](/docs/library/dynamic-rendering/auto-scroll/) component instead.

## rx.redirect

Redirect the user to a new path within the application.
Expand Down Expand Up @@ -86,6 +88,29 @@ This event allows you to copy a given text or content to the user's clipboard.
It's handy when you want to provide a "Copy to Clipboard" feature in your application,
allowing users to easily copy information to paste elsewhere.

`rx.set_clipboard` also accepts state Vars, so you can copy dynamic content,
such as the current value of an input field:

```python demo exec
class CopyState(rx.State):
text: str = ""

@rx.event
def set_text(self, value: str):
self.text = value


def copy_input_example():
return rx.hstack(
rx.input(
placeholder="Type something to copy",
value=CopyState.text,
on_change=CopyState.set_text,
),
rx.button("Copy", on_click=rx.set_clipboard(CopyState.text)),
)
```

## rx.set_value

Set the value of a specified reference element.
Expand Down Expand Up @@ -130,3 +155,26 @@ rx.button(
id="download button",
)
```

When the data to download is not already available at a known URL, return
`rx.download` from an event handler and pass the `data` directly:

```python demo exec
import random


class DownloadState(rx.State):
@rx.event
def download_random_data(self):
return rx.download(
data=",".join([str(random.randint(0, 100)) for _ in range(10)]),
filename="random_numbers.csv",
)


def download_random_data_button():
return rx.button(
"Download random numbers",
on_click=DownloadState.download_random_data,
)
```
39 changes: 39 additions & 0 deletions docs/events/page_load_events.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,42 @@ class State(rx.State):
def index():
return rx.text("A Beautiful App")
```

## Handling Loading and Errors

Avoid heavy synchronous work directly in an `on_load` handler — the page renders before the handler finishes, so a slow handler leaves the user staring at stale or empty data with no feedback. Instead:

- Use an async handler for network or database calls so the event loop is not blocked.
- Set a loading flag and `yield` to send it to the frontend immediately, then render a spinner or placeholder with `rx.cond`.
- Wrap the work in `try`/`except` so a failure surfaces as an error message instead of a page that never finishes loading.

```python
class DataState(rx.State):
data: dict = {}
loading: bool = False
error: str = ""

@rx.event
async def load_initial_data(self):
self.loading = True
yield # Send the loading state to the frontend immediately.
try:
self.data = await fetch_data()
except Exception as e:
self.error = str(e)
finally:
self.loading = False


@rx.page(on_load=DataState.load_initial_data)
def index():
return rx.cond(
DataState.loading,
rx.spinner(),
rx.cond(
DataState.error != "",
rx.text(f"Error: {DataState.error}"),
rx.text(f"Data loaded: {DataState.data}"),
),
)
```
51 changes: 51 additions & 0 deletions docs/library/forms/text_area.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,3 +90,54 @@ def feedback_form():
),
)
```

## Submitting on Enter

By default, pressing Enter in a text area inserts a new line. Set the
`enter_key_submit` prop to submit the enclosing form when Enter is pressed
instead. Shift+Enter still inserts a new line, so multi-line input remains
possible.

```python demo exec
class EnterSubmitState(rx.State):
submitted_text: str = ""

@rx.event
def handle_submit(self, form_data: dict):
self.submitted_text = form_data["message"]


def enter_key_submit_example():
return rx.vstack(
rx.cond(
EnterSubmitState.submitted_text != "",
rx.text("Submitted: ", EnterSubmitState.submitted_text),
rx.text("Nothing submitted yet."),
),
rx.form(
rx.text_area(
name="message",
placeholder="Type and press Enter to submit",
enter_key_submit=True,
),
on_submit=EnterSubmitState.handle_submit,
),
width="100%",
)
```

`enter_key_submit` accepts a Var, so it can be toggled dynamically — for
example, disabling Enter-to-submit while a previous submission is still being
processed: `enter_key_submit=~State.is_loading`.

```md alert warning
# `enter_key_submit` cannot be combined with `on_key_down`.

The `enter_key_submit` prop is implemented with its own key down handler, so
passing both to the same component raises an error. Note that a Python
`on_key_down` handler receives only the key and modifier flags, not the raw
event, so it cannot call `preventDefault()` to stop Enter from inserting a
newline. Replicating Enter-to-submit alongside custom key handling therefore
needs a client-side handler (e.g. via `rx.call_script`), not a plain
`on_key_down` event handler.
```
43 changes: 43 additions & 0 deletions docs/library/other/memo.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,49 @@ def index():
)
```

## Using with `rx.foreach`

To render a memoized component for each item of a list Var, wrap the call in a
lambda and pass the props by keyword. Also pass a `key` prop that uniquely
identifies each item — React uses it to track items across re-renders. For the
`key` to reach the rendered element, declare an `rx.RestProp` parameter and
spread it onto the returned component; `key` flows through the `...rest` spread
and React consumes it. Do **not** declare `key` itself in the memo's signature.

```python
from typing import TypedDict


class Task(TypedDict):
id: str
name: str


class TaskState(rx.State):
tasks: list[Task] = [
{"id": "1", "name": "Write docs"},
{"id": "2", "name": "Review PR"},
]


@rx.memo
def task_card(rest: rx.RestProp, *, task: rx.Var[Task]) -> rx.Component:
return rx.card(rx.text(task["name"]), rest)


def index():
return rx.vstack(
rx.foreach(
TaskState.tasks,
lambda task: task_card(task=task, key=task["id"]),
),
)
```
Comment thread
greptile-apps[bot] marked this conversation as resolved.

Inside the memo body, `task` is a `Var`, not a plain dict: index into it with
`task["name"]` or use it in f-strings, but do not iterate over it or call
Python dict methods like `.keys()` — only Var operations are available.

## 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.
Expand Down
Loading