Short answer
Reflex has no built-in configuration point for batching or rate-limiting state deltas. Each event handler runs to completion, the framework diffs the old and new state, and exactly one delta message is sent over the WebSocket per event (or per yield in an async generator handler). There is also no documented hard byte cap inside Reflex itself — the practical limits come from the Socket.IO/Engine.IO transport and whatever reverse proxy sits in front of your app. Prioritization and batching are application-level concerns.
What actually happens in the sync pipeline
Three facts worth separating from speculation:
- Batching already exists at the event boundary. If a handler makes ten assignments to state vars, the browser receives one delta containing the net diff, not ten messages. Intermediate assignments are never streamed.
- Async generators break that batching. Every
yield in an async handler emits its own delta. This is how token streaming works, but it also means a loop yielding 200 times sends 200 frames. - The size ceiling is external. The effective per-message limit is the Socket.IO
maxHttpBufferSize (commonly around 1 MB by default in the underlying Engine.IO stack) plus proxy limits such as Nginx buffer sizes and idle timeouts. Exact defaults vary by Reflex version and deployment, so treat any specific number as unverified until you check your stack.
Answering your three questions directly
1. Configuration points for delta batching
None are exposed by the framework. The knobs you do have are: (a) structure handlers so related mutations happen in one event, (b) avoid intermediate yield calls when you don't need progressive rendering, and (c) keep large payloads out of State entirely — store them server-side and fetch via an API route, or paginate/window the data so deltas stay small.
2. Prioritizing critical updates over trivial ones
There is no priority queue in the serialization layer. The practical workaround is to split concerns: put high-frequency trivial updates (slider positions, progress ticks) in a separate state var or component that you deliberately throttle, and keep critical changes in handlers that yield rarely. For high-frequency input, a common pattern is to debounce on the client side or accumulate values in a handler-local variable and assign to state once.
3. Concurrent modifications and lost updates
Reflex serializes event processing per client session — events from one browser session are processed against that session's state instance, and each completed event produces a consistent diff. The classic lost-update risk appears when you mutate shared data outside the per-session state (module-level globals, shared caches) from multiple handlers or background tasks; the framework's diffing won't protect you there. Keep mutable shared data behind explicit locking or in an external store.
Verifying the limits in your deployment
- Run
reflex --version and check the changelog for your version's Socket.IO/uvicorn configuration surface. - Write a test handler that assigns progressively larger lists to a state var, then watch browser DevTools → Network → WS frames to find where updates stall or the connection drops.
- If you deploy behind Nginx, Cloud Run, or similar, repeat the test through the proxy and inspect its logs — a limit you hit in production but not in
reflex run dev mode is almost always the proxy, not Reflex. - For streaming handlers, time round-trips with and without intermediate yields to decide whether immediacy or frame count matters more for your UI.
If deltas are large and frequent, the highest-leverage fix is almost always moving bulk data out of State rather than hunting for a batching knob that doesn't exist.