diff --git a/docs/enterprise/drag-and-drop.md b/docs/enterprise/drag-and-drop.md index 6a21d86314f..e17a99749ff 100644 --- a/docs/enterprise/drag-and-drop.md +++ b/docs/enterprise/drag-and-drop.md @@ -13,6 +13,8 @@ Reflex Enterprise provides comprehensive drag and drop functionality for creatin ```md alert warning # Important: Always decorate functions defining `rxe.dnd.draggable` components with `@rx.memo` to avoid compilation errors. + +See [memo](/docs/library/other/memo) for how `@rx.memo` components handle parameters. ``` ## Basic Usage @@ -335,6 +337,8 @@ The `rxe.dnd.draggable` component makes any element draggable: - `item`: Data object passed to drop handlers - `on_end`: Called when drag operation ends +There is no event that fires when a drag begins — `on_end`, which fires when the drag completes, is the only drag lifecycle event on the draggable. + ### Drop Target The `rxe.dnd.drop_target` component creates areas that accept draggable items: @@ -344,6 +348,8 @@ The `rxe.dnd.drop_target` component creates areas that accept draggable items: - `on_drop`: Called when item is dropped - `on_hover`: Called when item hovers over target +When drop targets are nested, a drop fires an event for each layer under the cursor, so deduplicate in the state handler (for example, with a timestamp or flag). To support dropping at the root level — such as the background of a file explorer — wrap the root container itself in a drop target. + ### Collected Parameters Access real-time drag/drop state: @@ -355,6 +361,37 @@ Access real-time drag/drop state: - `is_over`: Boolean indicating if draggable is hovering - `can_drop`: Boolean indicating if drop is allowed +```md alert warning +# Assign collected params to a variable first + +Do not read fields directly off the class (e.g. `rxe.dnd.DropTarget.is_over`). Assign `collected_params` to a variable, then access fields through it: `params = rxe.dnd.DropTarget.collected_params`, then `params.is_over`. +``` + +When a single `@rx.memo` component renders more than one draggable or drop target, give each its own collected params name to avoid JavaScript name collisions. Use `_replace(_js_expr=...)` to create a uniquely named params Var and pass it via `_collected_params`. Note that `_replace` and `_js_expr` are internal Reflex APIs and may change between releases: + +```python +@rx.memo +def dual_drop_zones() -> rx.Component: + left_params = rxe.dnd.DropTarget.collected_params._replace( + _js_expr="leftZoneParams" + ) + right_params = rxe.dnd.DropTarget.collected_params._replace( + _js_expr="rightZoneParams" + ) + return rx.hstack( + rxe.dnd.drop_target( + rx.box("Left", bg=rx.cond(left_params.is_over, "green.100", "gray.100")), + accept=["MyDraggable"], + _collected_params=left_params, + ), + rxe.dnd.drop_target( + rx.box("Right", bg=rx.cond(right_params.is_over, "green.100", "gray.100")), + accept=["MyDraggable"], + _collected_params=right_params, + ), + ) +``` + ## API Reference ### rxe.dnd.draggable @@ -572,6 +609,12 @@ def app(): ) ``` +```md alert warning +# Do not add a second provider + +Because the provider is added automatically when `draggable` or `drop_target` components are used, wrapping the app in an additional `rxe.dnd.provider` results in duplicate providers and breaks drag and drop. Only use manual control when the automatic provider does not fit (e.g. to select the touch backend), and make sure it is the only provider in the tree. +``` + ## Best Practices 1. **Always use `@rx.memo`** on functions containing draggable components diff --git a/docs/library/forms/upload.md b/docs/library/forms/upload.md index ee340206a58..956d2705d64 100644 --- a/docs/library/forms/upload.md +++ b/docs/library/forms/upload.md @@ -57,6 +57,8 @@ event args: - `rx.upload_files(upload_id=id)` for uploads - `rx.upload_files_chunk(upload_id=id)` for larger files that should be processed incrementally +The `id` links the upload component to these special Vars and event args, so they must all reference the same value. Pass it as a string literal or module-level constant — a state var cannot be used as an upload `id`. + ## File Storage Functions Reflex provides two key functions for handling uploaded files: @@ -70,10 +72,12 @@ Reflex provides two key functions for handling uploaded files: ### rx.get_upload_url(filename) -- **Purpose**: Returns the URL string that can be used in frontend components to access uploaded files +- **Purpose**: Returns the URL that can be used in frontend components to access uploaded files - **Usage**: Used in frontend components (like `rx.image`, `rx.video`) to display uploaded files - **URL Format**: `/_upload/filename` -- **Type**: Returns `str` +- **Type**: Returns a frontend `Var` (a JavaScript expression), not a Python `str` + +Because it returns a frontend Var, `rx.get_upload_url` is frontend-only: use it in component code and event triggers, not in backend event handlers or computed vars, where the value would not resolve to a usable string. If you genuinely need the upload URL as a plain string on the backend, build it from the configured upload endpoint — `Endpoint.UPLOAD.get_url()` (from `reflex.constants`) joined with the filename — which accounts for the scheme, any `backend_path` prefix, and split frontend/backend deployments. ### Key Differences @@ -435,12 +439,26 @@ rx.upload.root( ## Handling the Upload -For uploads, your event handler should be an async function that accepts a single argument, `files: list[UploadFile]`, which will contain [Starlette UploadFile](https://www.starlette.io/requests/#request-files) instances. You can read the files and save them anywhere as shown in the example. +For uploads, your event handler should be an async function that accepts a single argument, `files: list[UploadFile]`, which will contain [Starlette UploadFile](https://www.starlette.io/requests/#request-files) instances. Each file exposes its original filename as `file.name`. You can read the files and save them anywhere as shown in the example. + +```md alert warning +# Upload handlers are regular event handlers. +Do not declare a standard upload handler with `@rx.event(background=True)`, and do not use `async with self` inside it — those apply only to chunked upload handlers (see below). A standard handler is a plain `@rx.event` async function. +``` In your UI, you can bind the event handler to a trigger, such as a button `on_click` event or upload `on_drop` event, and pass in the files using `rx.upload_files()`. +```md alert warning +# Keep the upload trigger button outside the upload component. +The dropzone treats any click inside `rx.upload` / `rx.upload.root` as a request to open the file picker. If the button that triggers the upload event is placed inside, each click both opens the picker and starts the upload. Buttons inside the dropzone (like "Select File" in the examples above) should be purely visual. +``` + +### Uploading from a Form + +A form's `on_submit` event never includes uploaded file data. When an upload component lives inside a form, trigger the upload from a separate button with `type="button"` (or from the dropzone's `on_drop` trigger) using `rx.upload_files()`, and handle the remaining form fields in `on_submit` as usual. + ### Saving the File By convention, Reflex provides the function `rx.get_upload_dir()` to get the directory where uploaded files may be saved. The upload dir comes from the environment variable `REFLEX_UPLOADED_FILES_DIR`, or `./uploaded_files` if not specified. @@ -470,6 +488,8 @@ The files are automatically served at: - `/_upload/document.pdf` ← `rx.get_upload_url("document.pdf")` - `/_upload/video.mp4` ← `rx.get_upload_url("video.mp4")` +These paths are an implementation detail — avoid hardcoding `/upload/` or `/_upload/` in your app. Go through `rx.get_upload_url(filename)` so URLs remain correct when the frontend and backend are served from different hosts or ports. + ### Chunked Uploads for Large Files Use `rx.upload_files_chunk(...)` when files may be large or when you want the backend to write data incrementally. Standard uploads spool files to disk before the handler starts, but calling `await file.read()` in the handler loads the entire file into memory at once, which can cause high memory consumption for large files. diff --git a/docs/library/typography/markdown.md b/docs/library/typography/markdown.md index 915d96349c5..8c4cf1ae7b9 100644 --- a/docs/library/typography/markdown.md +++ b/docs/library/typography/markdown.md @@ -31,6 +31,11 @@ For inline equations, surround the equation with `$`: rx.markdown("Pythagorean theorem: $a^2 + b^2 = c^2$.") ``` +```md alert warning +# Escaping dollar signs +Because `$` is used as the math delimiter, any pair of `$` characters in the content is interpreted as an inline LaTeX equation — the text between them may disappear or render as a garbled formula. This commonly affects prices and other monetary values, especially in dynamic content such as streamed LLM output. To render literal dollar signs, escape them before passing the text to `rx.markdown`, e.g. `content.replace("$", "\\$")`. +``` + ## Syntax Highlighting You can render code blocks with syntax highlighting using the \`\`\`{language} syntax: diff --git a/docs/state_structure/shared_state.md b/docs/state_structure/shared_state.md index feb55935611..428b5ed3012 100644 --- a/docs/state_structure/shared_state.md +++ b/docs/state_structure/shared_state.md @@ -24,12 +24,22 @@ A token can be any string that uniquely identifies a group of clients that shoul Underscore characters are currently used as an internal delimiter for tokens and will raise an exception if used for linked states. This is a temporary restriction and will be removed in a future release. + +When deriving a token from user input, such as a query parameter, sanitize it before linking — for example `raw_token.replace("_", "-")` — and fall back to a default value when the parameter is empty or missing. ``` ### Linking Shared State An `rx.SharedState` subclass can be linked to a token using the `_link_to` method, which is async and returns the linked state instance. After linking, subsequent events triggered against the shared state will be executed in the context of the linked state. To unlink from the token, return the result of awaiting the `_unlink` method. +```md alert warning +# Common linking pitfalls + +- `_link_to` is a coroutine — forgetting to `await` it silently skips linking. Any changes made to a shared state before it is linked are not shared; they apply only to the client's private instance. +- `await self._link_to(...)` returns the newly linked instance. Calling methods on `self` immediately afterwards still mutates the pre-link state, so save the returned instance and use it for any actions that should happen after linking. +- `_link_to` is only defined on `rx.SharedState` subclasses. To link from a regular `rx.State` event handler, first fetch the shared state with `await self.get_state(SharedStateCls)`, then call `_link_to` on that instance. +``` + To try out the collaborative counter example, open this page in a second or third browser tab and click the "Link" button. You should see the count increment in all tabs when you click the "Increment" button in any of them. ```python demo exec @@ -74,6 +84,8 @@ def shared_state_example(): Computed vars in other states may reference shared state data using `get_state`, just like private states. This allows private states to provide personalized views of shared data. Whenever the shared state is updated, any computed vars depending on it will be re-evaluated in the context of each client's private state. + +Note that a computed var that depends only on shared state vars belongs on the `rx.SharedState` itself — reserve private-state computed vars for values that also depend on per-user data. ``` ### Identifying Clients diff --git a/docs/utility_methods/lifespan_tasks.md b/docs/utility_methods/lifespan_tasks.md index 9945e121a36..0fcd2222a4b 100644 --- a/docs/utility_methods/lifespan_tasks.md +++ b/docs/utility_methods/lifespan_tasks.md @@ -14,6 +14,24 @@ Tasks execute in the order they are registered. In dev mode, lifespan tasks will stop and restart when a hot-reload occurs. +## Lifespan Tasks vs Background Events + +Lifespan tasks are not the only way to run work outside the normal event flow. +Choose based on what starts the work and what it operates on: + +- Use a **lifespan task** for automatic, continuous, application-wide work that + runs independent of any user or session — it starts when the app starts and is + not tied to UI state. Examples: polling an external API on an interval, + monitoring a service, refreshing a shared cache, scheduled maintenance. +- Use a [background event](/docs/events/background-events) + (`@rx.event(background=True)`) for user-triggered, session-bound work that + reads or updates that user's UI state. Examples: processing a file after a + user clicks submit, calling an API on demand, showing progress during a + long-running operation the user initiated. + +For instance, "check an external API every 5 minutes" is a lifespan task, while +"process data when the user clicks submit" is a background event. + ## Tasks Any async coroutine can be used as a lifespan task. It will be started when the