Skip to content

Conversation

@aibysid
Copy link

@aibysid aibysid commented Nov 20, 2025

### All Submissions:

- [x] Have you followed the guidelines stated in [CONTRIBUTING.md](https://github.com/reflex-dev/reflex/blob/main/CONTRIBUTING.md) file?
- [x] Have you checked to ensure there aren't any other open [Pull Requests](https://github.com/reflex-dev/reflex/pulls) for the desired changed?

### Type of change

- [x] New feature (non-breaking change which adds functionality)
- [x] Bug fix (non-breaking change which fixes an issue)

### New Feature Submission:

- [x] Does your submission pass the tests?
- [x] Have you linted your code locally prior to submission?

### Changes To Core Features:

- [x] Have you added an explanation of what your changes do and why you'd like us to include them?
- [x] Have you written new tests for your core changes, as applicable?
- [x] Have you successfully ran tests with your changes locally?

---

### Description

This PR enables **Computed Vars** to reliably trigger side effects, such as yielding events or modifying other state variables, during their computation.

Previously, computed vars were expected to be pure functions. Attempting to `yield` events (like `rx.toast`) resulted in the property returning a generator object instead of the calculated value. Additionally, if a computed var modified another state variable, that change was often missed in the current state update cycle because `get_delta` did not re-evaluate dependencies after the initial computation.

**Key Changes:**
1.  **Support for Generator Computed Vars**: Updated `ComputedVar.__get__` (in `reflex/vars/base.py`) to detect generator return values. It now iterates through the generator, collects yielded events, and returns the final value (captured from `StopIteration`).
2.  **Event Collection**: Added `_computed_var_events` to `BaseState` to store these yielded events and updated `_as_state_update` to include them in the response sent to the frontend.
3.  **Iterative State Updates**: Modified `BaseState.get_delta` (in `reflex/state.py`) to iterate (up to a limit) when calculating the delta. This ensures that if a computed var modifies a base var (marking it dirty), the loop continues to capture the ripple effects of that change in the same update cycle.

### Example Usage

```python
class MyState(rx.State):
    count: int = 0
    status: str = "Normal"

    @rx.var
    def check_status(self) -> str:
        if self.count > 10:
            # This side effect (event) is now captured
            yield rx.toast("Count is too high!")
            
            # This side effect (state modification) is now reliably captured in the same update
            self.status = "High" 
            return "Alert"
        return "OK"

Tests

Added tests/units/test_computed_var_side_effects.py which verifies:

  • A computed var correctly yields an event (e.g., rx.window_alert).
  • A computed var correctly modifies another state variable, and the change is present in the state delta.

Closes #5956

@aibysid
Copy link
Author

aibysid commented Nov 20, 2025

One more test done by actual dummy app :
Screenshot 2025-11-20 at 16 58 12
Screenshot 2025-11-20 at 16 58 05

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Nov 20, 2025

Greptile Overview

Greptile Summary

This PR enables computed vars to yield events and modify state during computation. The implementation adds generator detection in ComputedVar.__get__, introduces _computed_var_events collection, and adds a stabilization loop in get_delta() to capture cascading state changes.

Key changes:

  • Computed vars can now yield events which are collected and sent to the frontend
  • State modifications within computed vars are now captured via iterative delta calculation
  • Added _needs_update vs _needs_update_check to handle caching correctly during stabilization

Critical issues found:

  • subdelta dictionary is recreated in each loop iteration, causing state updates from earlier iterations to be lost
  • Debug print statement left in production code

Confidence Score: 0/5

  • This PR has a critical bug that will cause data loss in production
  • The subdelta variable is recreated inside the stabilization loop, which means only the final iteration's changes will be included in the delta. This defeats the entire purpose of the stabilization loop and will cause state updates from earlier iterations to be completely lost. Additionally, a debug print statement was left in production code.
  • reflex/state.py requires immediate attention to fix the subdelta accumulation bug before merging

Important Files Changed

File Analysis

Filename Score Overview
reflex/vars/base.py 3/5 Adds generator support to computed vars with debug print statement left in code
reflex/state.py 1/5 Adds stabilization loop to get_delta but has critical bug losing state updates from earlier iterations
tests/units/test_computed_var_side_effects.py 4/5 New test file covering computed var side effects including events and state modifications

Sequence Diagram

sequenceDiagram
    participant User
    participant EventHandler
    participant State
    participant ComputedVar
    participant Frontend

    User->>EventHandler: Trigger event (e.g., set count=5)
    EventHandler->>State: Process event
    State->>State: _get_resolved_delta()
    State->>State: get_delta()
    
    loop Stabilization (max 10 iterations)
        State->>State: _mark_dirty_computed_vars()
        State->>ComputedVar: get_value(computed_var)
        ComputedVar->>ComputedVar: _get_value() executes fget
        
        alt Generator detected
            ComputedVar->>ComputedVar: Iterate generator
            ComputedVar->>State: Collect yielded events to _computed_var_events
            ComputedVar->>State: Return final value from StopIteration
        else Regular value
            ComputedVar->>State: Return value
        end
        
        alt Computed var modifies state
            ComputedVar->>State: Modify base vars (e.g., side_effect_value = "Five")
            State->>State: Mark modified vars dirty
            Note over State: Continue loop if new dirty_vars detected
        end
        
        State->>State: Build subdelta with dirty vars
    end
    
    State->>State: _collect_computed_var_events()
    State-->>EventHandler: Return delta + computed_var_events
    EventHandler->>EventHandler: fix_events(computed_var_events)
    EventHandler->>Frontend: Send StateUpdate(delta, events)
Loading

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

3 files reviewed, 3 comments

Edit Code Review Agent Settings | Greptile

Returns:
The value of the computed var.
"""
print(f"DEBUG: Computing {self._name} for {type(instance).__name__}")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

syntax: Debug print statement left in production code

Suggested change
print(f"DEBUG: Computing {self._name} for {type(instance).__name__}")

Context Used: Rule from dashboard - Remove commented-out code before merging PRs. (source)

Prompt To Fix With AI
This is a comment left during a code review.
Path: reflex/vars/base.py
Line: 2446:2446

Comment:
**syntax:** Debug print statement left in production code

```suggestion
```

**Context Used:** Rule from `dashboard` - Remove commented-out code before merging PRs. ([source](https://app.greptile.com/review/custom-context?memory=d49e2a0e-27a4-4cd6-b764-58c8a6fc4032))

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +2094 to +2098
subdelta: dict[str, Any] = {
prop + FIELD_MARKER: self.get_value(prop)
for prop in delta_vars
if not types.is_backend_base_variable(prop, type(self))
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: subdelta is recreated in each loop iteration, so only the last iteration's changes will be included in the final delta. This will cause state updates from earlier iterations to be lost.

Move this outside the loop and update it incrementally:

Suggested change
subdelta: dict[str, Any] = {
prop + FIELD_MARKER: self.get_value(prop)
for prop in delta_vars
if not types.is_backend_base_variable(prop, type(self))
}
# Only compute delta on first iteration or when there are new dirty vars
if i == 0 or new_dirty_vars:
for prop in delta_vars:
if not types.is_backend_base_variable(prop, type(self)):
subdelta[prop + FIELD_MARKER] = self.get_value(prop)
Prompt To Fix With AI
This is a comment left during a code review.
Path: reflex/state.py
Line: 2094:2098

Comment:
**logic:** `subdelta` is recreated in each loop iteration, so only the last iteration's changes will be included in the final delta. This will cause state updates from earlier iterations to be lost.

Move this outside the loop and update it incrementally:

```suggestion
            # Only compute delta on first iteration or when there are new dirty vars
            if i == 0 or new_dirty_vars:
                for prop in delta_vars:
                    if not types.is_backend_base_variable(prop, type(self)):
                        subdelta[prop + FIELD_MARKER] = self.get_value(prop)
```

How can I resolve this? If you propose a fix, please make it concise.

Returns:
The delta for the state.
"""
delta = {}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: subdelta needs to be initialized outside the loop to accumulate changes across iterations

Suggested change
delta = {}
delta = {}
subdelta: dict[str, Any] = {}
Prompt To Fix With AI
This is a comment left during a code review.
Path: reflex/state.py
Line: 2067:2067

Comment:
**logic:** `subdelta` needs to be initialized outside the loop to accumulate changes across iterations

```suggestion
        delta = {}
        subdelta: dict[str, Any] = {}
```

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Collaborator

@masenf masenf left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this feels a bit hacky, but i'm willing to entertain it for now since my larger event processing refactor will take some more time to be ready.

greptile makes valid comments that should be addressed here, particularly the subdelta one

@codspeed-hq
Copy link

codspeed-hq bot commented Nov 20, 2025

CodSpeed Performance Report

Merging #5990 will not alter performance

Comparing aibysid:fix-computed-var-side-effects (e61f8f6) with main (a987437)

Summary

✅ 8 untouched

@aibysid
Copy link
Author

aibysid commented Nov 20, 2025

this feels a bit hacky, but i'm willing to entertain it for now since my larger event processing refactor will take some more time to be ready.

greptile makes valid comments that should be addressed here, particularly the subdelta one

Yeah I agree that got overlooked will try to make a new commit with those getting addressed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Should be able to yield chained events from a computed var

2 participants