Diagnosing Reflex Computed Vars That Fail to Update the UI
A practical guide to diagnosing Reflex computed vars that render stale data, covering assignment-based dirtiness, decorator selection, and WebSocket delta verification.
16 Dec 2025, 19:53 UTC

The symptom: stale derived data after an event
You have a Reflex computed var that derives a value from state—say a filtered list or a formatted total. An event handler runs, the underlying base var changes, but the component keeps rendering the old derived value. The useful takeaway is that Reflex marks state dirty by assignment, not by in-place mutation. If your handler mutates a list or dict in place, the backend never marks the dependency dirty, so the computed var is never recomputed and no delta is sent to the browser.
Cause and diagnostic signals
| Likely cause | Diagnostic signal |
|---|---|
| In-place mutation of a mutable base var | No WebSocket delta frame appears after the event; the computed var is not accessed again |
| Wrong computed var decorator for the use case | Standard decorator recomputes on every access; cached decorator memoizes until dependencies are marked dirty |
| Async computed var rendered as a coroutine | UI shows a coroutine object repr or blank space instead of the resolved value |
| Computed var used with arguments | Unexpected behavior or errors; computed vars cannot accept parameters |
| Per-session state masking the bug | Update appears in one browser tab but not another because each tab has its own State instance |
Ordered checks
- Confirm the handler runs and assigns. Add a temporary
print()or logger inside the event handler and inside the computed var. If the handler runs but the computed var is not accessed afterward, the dependency was not marked dirty. - Inspect WebSocket frames. Open browser devtools, go to the Network/WS tab, trigger the event, and look for a delta patch. Absence of a frame means the backend never marked the state dirty.
- Swap the computed var for the base var. Temporarily change the component to render the base var directly. If the UI updates, the bug is isolated to the computed var or its dependency tracking.
- Verify decorator names and import paths. Reflex renamed several APIs between versions. Confirm whether your code imports from
reflexor the legacypyneconepackage, and check the exact decorator names against your installed version.
Check installed version and legacy imports
Run these in your project root with the same Python environment that runs the app:
python -c "import reflex; print(reflex.__version__)"
grep -r "from pynecone" . --include="*.py"
Expected checks: The first command prints the installed Reflex version. The second lists any files still importing from pynecone. Risk: If multiple virtual environments exist, ensure you activate the correct one first; otherwise the version reported may not match the running backend.
Fixes tied to their findings
Fix 1: Reassign instead of mutating in place
If the check in step 2 showed no WebSocket delta, the handler likely mutated a mutable object without reassigning the parent var. Reflex tracks dirtiness by assignment. For example, avoid self.items.append(new_item); instead assign a new list:
class MyState(rx.State):
items: list[str] = []
filter_text: str = ""
@rx.var
def visible_items(self) -> list[str]:
return [i for i in self.items if self.filter_text in i]
def add_item(self, item: str):
# Wrong: self.items.append(item)
self.items = self.items + [item]
The same rule applies to dicts and sets: create a new object and assign it to the base var so the dependency graph is notified.
Fix 2: Match the decorator to the recomputation cost
Reflex provides a standard computed var decorator that recomputes on every access, and cached variants that memoize the result until dependencies are marked dirty. Use the standard decorator for cheap derivations that must always reflect the latest state. Use a cached decorator only for expensive derivations whose inputs change infrequently. If a cached computed var renders stale data, verify that every dependency is assigned rather than mutated; otherwise the cache is never invalidated.
Fix 3: Let the framework resolve async computed vars
Async computed vars require the framework to await the coroutine. If a component renders the coroutine object itself, the UI shows an object repr or nothing until a later render cycle. Reference the computed var as a property in your component and ensure the handler does not accidentally pass the coroutine into a non-async prop. If you need parameterized logic, move it into a regular method or event handler; computed vars cannot accept arguments.
Fix 4: Isolate with a minimal State subclass
If the above fixes do not resolve the issue, create a minimal State subclass containing only the affected vars and reproduce the behavior in a clean app run. This isolates inheritance or middleware interference.
Escalation criteria
- Minimal reproduction still fails. If a State subclass with only the affected vars still produces no WebSocket delta after a confirmed assignment, the problem may be framework-level or caused by middleware.
- Backend exceptions during events. If deltas are absent and the handler logs show no assignment, inspect backend logs for exceptions raised before the assignment line.
- Cached data survives restarts. If a cached computed var persists stale values after a backend restart, suspect external state, accidental global caching, or a browser tab holding an old session.
Limitations and verification
Reflex state is per-session: opening multiple browser tabs creates separate State instances and WebSocket connections, which can mask update bugs during manual testing. Always verify in a single tab. Computed vars cannot accept arguments; if your derivation needs parameters, use an event handler or regular method instead.
To verify the fix, trigger the event and confirm two things in the browser devtools WebSocket tab: a delta frame appears containing the changed base var, and the component re-renders with the new derived value. As a secondary check, keep the temporary logger inside the computed var; it should execute after the event, proving the dependency was marked dirty and the cache was invalidated.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.