Skip to content

fix: raise BackendVarFormatError for the remaining backend var UI uses - #7908

Open
ShaanGS wants to merge 2 commits into
reflex-dev:mainfrom
ShaanGS:fix/backend-var-ui-errors
Open

ShaanGS wants to merge 2 commits into
reflex-dev:mainfrom
ShaanGS:fix/backend-var-ui-errors

Conversation

@ShaanGS

@ShaanGS ShaanGS commented Nov 15, 2026 •

Copy link
Copy Markdown
Contributor

All Submissions:

  • Have you followed the guidelines stated in CONTRIBUTING.md file?
  • Have you checked to ensure there aren't any other open Pull Requests for the desired changed?

Type of change

  • Bug fix (non-breaking change which fixes an issue)

Changes To Core Features:

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

Description

Closes #7843.

Every row in the issue now raises the same BackendVarFormatError as the other UI paths, and none of them prints the default value:

  • children and typed props check for a Field before the generic checks. The meta row goes through the typed prop check, so it raises when the page compiles.
  • event arguments re-raise the error that LiteralVar.create already builds, instead of wrapping it in EventHandlerTypeError.
  • add_page checks on_load, the same way it checks title, description and image.
  • Field.__eq__ raises when a backend var is compared with something that isn't a field, and __bool__ raises for a backend var. Field-to-field comparison stays identity, and __hash__ stays object.__hash__, so dict keys, sets and in checks over fields work as before.

The comparison and bool checks also cover a mixin's fields, since formatting one already raises the mixin message. I kept the check off ordering (State._n > 0) and in, which already fail with a plain TypeError rather than silently.

Tests: one per row, plus mixins, field identity and hashing. The 13 new or changed tests fail on main and pass with the fix. The full unit suite has the same failures as main (only the pyi_generator tests, which fail on my machine with or without this change). I also updated the two upgrading guides that described the old errors.

View guided diff

A backend var used as a component child raised a ChildrenTypeError that
named neither the var nor a fix, and as a typed prop or an event handler
argument a generic error that printed its default value. Comparing it (or
a mixin's field) with a value or truth-testing it gave a plain False or
True, so rx.cond on it always took one branch, and on_load accepted it.
These now raise BackendVarFormatError. Fields still compare and hash by
identity.

Closes reflex-dev#7843.
@ShaanGS
ShaanGS requested review from a team and Alek99 as code owners November 15, 2026 09:17
@greptile-apps

greptile-apps Bot commented Nov 15, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerView in GreptileConfidence Score: 4/5

[Medium impact] The PR changes how backend-only fields behave across shared UI and page setup paths.

The full-message assertions required by the repository must be added before merging.

Findings

  1. P2 Error checks miss message changes ▶

Summary

This PR makes more backend-var UI mistakes raise BackendVarFormatError with guidance on how to fix them.

  • Components and event arguments give backend-only fields one clear error.
  • Backend-only fields no longer compare or test as ordinary values.
  • Pages reject backend fields used as load handlers.

The new error-message tests must check the full message to meet the repository's testing rule.

Reviews (1) · Last reviewed commit: "fix: raise BackendVarFormatError for the..." · Reviewed by Greptile

Comment on lines +873 to +878
with pytest.raises(
BackendVarFormatError,
match=r"Backend var 'BackendPropState\._size' exists only",
) as exc_info:
create(BackendPropState._size)
assert "default=" not in str(exc_info.value)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Error checks miss message changes. These new assertions match only the start of the error, so they still pass if the suggested fix disappears or becomes wrong. The repository requires tests to compare complete error messages and explicitly forbids partial regex matches. Compare the full expected message here and in the other new checks, including those in test_base.py and test_app.py.

Context Used: CLAUDE.md (source)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

5 issues found across 12 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="tests/units/test_app.py">

<violation number="1" location="tests/units/test_app.py:352">
P3: The new `pytest.raises` calls match only a fragment of the error message via a partial regex. Per `AGENTS.md`, assert the full error message exactly, so a changed or truncated message is caught. Please match the complete expected text in both new tests.</violation>
</file>

<file name="tests/units/test_event.py">

<violation number="1" location="tests/units/test_event.py:278">
P3: This `match` is a partial regex, so the test passes for any message starting with this prefix. AGENTS.md asks for the full error message to be compared exactly. Match the complete `BackendVarFormatError` text from `_no_frontend_var_error`, with the owner and field names fixed.</violation>
</file>

<file name="docs/changelog/upgrading/upgrading-to-0-11.md">

<violation number="1" location="docs/changelog/upgrading/upgrading-to-0-11.md:244">
P3: Logging does not raise here. With %-style arguments, the stdlib logging handler catches the error and prints a `--- Logging error ---` report. Name `print()` and `str()` as the raising cases, and say that `repr()` is the way to log the field.</violation>
</file>

<file name="docs/changelog/upgrading/upgrading-to-0-10.md">

<violation number="1" location="docs/changelog/upgrading/upgrading-to-0-10.md:12">
P2: This paragraph documents the `str()`/f-string raising as 0.10 behavior, but the 0.11 guide and the v0.11.0 changelog put it in 0.11, and v0.10.1 does not mention it. Move the `str()`/f-string sentence to the 0.11 guide only, or correct the version it names.</violation>
</file>

<file name="tests/units/components/test_component.py">

<violation number="1" location="tests/units/components/test_component.py:799">
P3: This prefix-only `match` passes even if the rest of the error, including its suggested fix, changes. Assert the complete expected message here and in the other new backend-var error checks.</violation>
</file>

Reply with feedback, questions, or to request a fix.

View guided diff | Re-trigger cubic

## Reading a backend var on a state class

Reading a backend var on the state class, such as `State._items`, now returns its `Field` instead of the var's default value ([#7312](https://github.com/reflex-dev/reflex/pull/7312)). Passing it to a component raises `ChildrenTypeError` (as a child); a prop may raise `BackendVarFormatError` when its value is built or rendered (earlier 0.10 releases may instead raise `TypeError: Unsupported type <class 'reflex_base.vars.base.Field'> for LiteralVar`), while typed props can still reject it with `TypeError` during component creation. Turning it into text with an f-string, `str.format()`, `str()` or `%` raises `BackendVarFormatError` (earlier 0.10 releases embed the `Field` text for `!s`, `str()` and `%`; `!r` and `repr()` still do). Reading the var on a state instance, such as `self._items`, is unchanged.
Reading a backend var on the state class, such as `State._items`, now returns its `Field` instead of the var's default value ([#7312](https://github.com/reflex-dev/reflex/pull/7312)). Passing it to a component as a child or a prop raises `BackendVarFormatError` (earlier releases may instead raise `ChildrenTypeError` for a child, `TypeError` for a typed prop, or `TypeError: Unsupported type <class 'reflex_base.vars.base.Field'> for LiteralVar` when the value is built or rendered). Turning it into text with an f-string, `str.format()`, `str()` or `%` raises `BackendVarFormatError` (earlier 0.10 releases embed the `Field` text for `!s`, `str()` and `%`; `!r` and `repr()` still do). Reading the var on a state instance, such as `self._items`, is unchanged.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2: This paragraph documents the str()/f-string raising as 0.10 behavior, but the 0.11 guide and the v0.11.0 changelog put it in 0.11, and v0.10.1 does not mention it. Move the str()/f-string sentence to the 0.11 guide only, or correct the version it names.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At docs/changelog/upgrading/upgrading-to-0-10.md, line 12:

<comment>This paragraph documents the `str()`/f-string raising as 0.10 behavior, but the 0.11 guide and the v0.11.0 changelog put it in 0.11, and v0.10.1 does not mention it. Move the `str()`/f-string sentence to the 0.11 guide only, or correct the version it names.</comment>

<file context>
@@ -9,7 +9,7 @@ This page covers the 0.10 changes most likely to break an existing app, or to be
 ## Reading a backend var on a state class
 
-Reading a backend var on the state class, such as `State._items`, now returns its `Field` instead of the var's default value ([#7312](https://github.com/reflex-dev/reflex/pull/7312)). Passing it to a component raises `ChildrenTypeError` (as a child); a prop may raise `BackendVarFormatError` when its value is built or rendered (earlier 0.10 releases may instead raise `TypeError: Unsupported type <class 'reflex_base.vars.base.Field'> for LiteralVar`), while typed props can still reject it with `TypeError` during component creation. Turning it into text with an f-string, `str.format()`, `str()` or `%` raises `BackendVarFormatError` (earlier 0.10 releases embed the `Field` text for `!s`, `str()` and `%`; `!r` and `repr()` still do). Reading the var on a state instance, such as `self._items`, is unchanged.
+Reading a backend var on the state class, such as `State._items`, now returns its `Field` instead of the var's default value ([#7312](https://github.com/reflex-dev/reflex/pull/7312)). Passing it to a component as a child or a prop raises `BackendVarFormatError` (earlier releases may instead raise `ChildrenTypeError` for a child, `TypeError` for a typed prop, or `TypeError: Unsupported type <class 'reflex_base.vars.base.Field'> for LiteralVar` when the value is built or rendered). Turning it into text with an f-string, `str.format()`, `str()` or `%` raises `BackendVarFormatError` (earlier 0.10 releases embed the `Field` text for `!s`, `str()` and `%`; `!r` and `repr()` still do). Reading the var on a state instance, such as `self._items`, is unchanged.
 
 Replace the class read with what the code meant:
</file context>

Comment thread tests/units/test_app.py
)
with pytest.raises(
exceptions.BackendVarFormatError,
match=r"Backend var 'BackendMetaState\._meta' exists only",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P3: The new pytest.raises calls match only a fragment of the error message via a partial regex. Per AGENTS.md, assert the full error message exactly, so a changed or truncated message is caught. Please match the complete expected text in both new tests.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At tests/units/test_app.py, line 352:

<comment>The new `pytest.raises` calls match only a fragment of the error message via a partial regex. Per `AGENTS.md`, assert the full error message exactly, so a changed or truncated message is caught. Please match the complete expected text in both new tests.</comment>

<file context>
@@ -333,6 +333,46 @@ def test_add_page_backend_var_meta_raises_format_error(
+    )
+    with pytest.raises(
+        exceptions.BackendVarFormatError,
+        match=r"Backend var 'BackendMetaState\._meta' exists only",
+    ):
+        app._compile_page("meta")
</file context>

Comment thread tests/units/test_event.py
S.on_event(S._secret)
with pytest.raises(EventHandlerTypeError, match=r"Got Field\(default=42"):
cast(EventSpec, S.on_event()).add_args(S._secret) # pyright: ignore[reportArgumentType]
match = r"Backend var 'BackendEventArgState\._secret' exists only"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P3: This match is a partial regex, so the test passes for any message starting with this prefix. AGENTS.md asks for the full error message to be compared exactly. Match the complete BackendVarFormatError text from _no_frontend_var_error, with the owner and field names fixed.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At tests/units/test_event.py, line 278:

<comment>This `match` is a partial regex, so the test passes for any message starting with this prefix. AGENTS.md asks for the full error message to be compared exactly. Match the complete `BackendVarFormatError` text from `_no_frontend_var_error`, with the owner and field names fixed.</comment>

<file context>
@@ -266,20 +265,23 @@ def args_spec(value: Var[dict]) -> list[Var[dict]]:
-        S.on_event(S._secret)
-    with pytest.raises(EventHandlerTypeError, match=r"Got Field\(default=42"):
-        cast(EventSpec, S.on_event()).add_args(S._secret)  # pyright: ignore[reportArgumentType]
+    match = r"Backend var 'BackendEventArgState\._secret' exists only"
+    with pytest.raises(BackendVarFormatError, match=match):
+        BackendEventArgState.on_event(BackendEventArgState._secret)
</file context>

## Printing a backend var on a state class

A backend var read on the state class, such as `State._secret`, is a `Field` since 0.10. In 0.11, `print()`, logging and `str()` of it raise `BackendVarFormatError`, as f-strings already did ([#7651](https://github.com/reflex-dev/reflex/pull/7651)). The same error replaces the `TypeError` or `ValueError` raised when the var is passed where a frontend value is needed, such as a component prop, `rx.toast(State._secret)`, or the `title`, `description` or `image` of `app.add_page(...)`.
A backend var read on the state class, such as `State._secret`, is a `Field` since 0.10. In 0.11, `print()`, logging and `str()` of it raise `BackendVarFormatError`, as f-strings already did ([#7651](https://github.com/reflex-dev/reflex/pull/7651)). The same error replaces the `TypeError` or `ValueError` raised when the var is passed where a frontend value is needed, such as a component child or prop, an event handler argument, `rx.toast(State._secret)`, or the `title`, `description`, `image` or `on_load` of `app.add_page(...)`. Comparing it with a value, as in `rx.cond(State._secret == "x", ...)`, or truth-testing it also raises, where it used to give a plain `False` or `True`.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P3: Logging does not raise here. With %-style arguments, the stdlib logging handler catches the error and prints a --- Logging error --- report. Name print() and str() as the raising cases, and say that repr() is the way to log the field.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At docs/changelog/upgrading/upgrading-to-0-11.md, line 244:

<comment>Logging does not raise here. With %-style arguments, the stdlib logging handler catches the error and prints a `--- Logging error ---` report. Name `print()` and `str()` as the raising cases, and say that `repr()` is the way to log the field.</comment>

<file context>
@@ -241,7 +241,7 @@ def serialize_point(point: Point) -> dict:
 ## Printing a backend var on a state class
 
-A backend var read on the state class, such as `State._secret`, is a `Field` since 0.10. In 0.11, `print()`, logging and `str()` of it raise `BackendVarFormatError`, as f-strings already did ([#7651](https://github.com/reflex-dev/reflex/pull/7651)). The same error replaces the `TypeError` or `ValueError` raised when the var is passed where a frontend value is needed, such as a component prop, `rx.toast(State._secret)`, or the `title`, `description` or `image` of `app.add_page(...)`.
+A backend var read on the state class, such as `State._secret`, is a `Field` since 0.10. In 0.11, `print()`, logging and `str()` of it raise `BackendVarFormatError`, as f-strings already did ([#7651](https://github.com/reflex-dev/reflex/pull/7651)). The same error replaces the `TypeError` or `ValueError` raised when the var is passed where a frontend value is needed, such as a component child or prop, an event handler argument, `rx.toast(State._secret)`, or the `title`, `description`, `image` or `on_load` of `app.add_page(...)`. Comparing it with a value, as in `rx.cond(State._secret == "x", ...)`, or truth-testing it also raises, where it used to give a plain `False` or `True`.
 
 ```python
</file context>
Suggested change
A backend var read on the state class, such as `State._secret`, is a `Field` since 0.10. In 0.11, `print()`, logging and `str()` of it raise `BackendVarFormatError`, as f-strings already did ([#7651](https://github.com/reflex-dev/reflex/pull/7651)). The same error replaces the `TypeError` or `ValueError` raised when the var is passed where a frontend value is needed, such as a component child or prop, an event handler argument, `rx.toast(State._secret)`, or the `title`, `description`, `image` or `on_load` of `app.add_page(...)`. Comparing it with a value, as in `rx.cond(State._secret == "x", ...)`, or truth-testing it also raises, where it used to give a plain `False` or `True`.
A backend var read on the state class, such as `State._secret`, is a `Field` since 0.10. In 0.11, `print()`, `str()` and f-strings of it raise `BackendVarFormatError` ([#7651](https://github.com/reflex-dev/reflex/pull/7651)). Logging it with an f-string raises too, but logging with `%s` arguments does not raise to the caller. The same error replaces the `TypeError` or `ValueError` raised when the var is passed where a frontend value is needed, such as a component child or prop, an event handler argument, `rx.toast(State._secret)`, or the `title`, `description`, `image` or `on_load` of `app.add_page(...)`. Comparing it with a value, as in `rx.cond(State._secret == "x", ...)`, or truth-testing it also raises, where it used to give a plain `False` or `True`.

with pytest.raises(ChildrenTypeError, match=r"received child of type Field\."):
with pytest.raises(
BackendVarFormatError,
match=r"Backend var 'BackendChildState\._secret' exists only",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P3: This prefix-only match passes even if the rest of the error, including its suggested fix, changes. Assert the complete expected message here and in the other new backend-var error checks.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At tests/units/components/test_component.py, line 799:

<comment>This prefix-only `match` passes even if the rest of the error, including its suggested fix, changes. Assert the complete expected message here and in the other new backend-var error checks.</comment>

<file context>
@@ -789,12 +789,15 @@ def test_component_create_unallowed_types(children, test_component):
-    with pytest.raises(ChildrenTypeError, match=r"received child of type Field\."):
+    with pytest.raises(
+        BackendVarFormatError,
+        match=r"Backend var 'BackendChildState\._secret' exists only",
+    ):
         test_component.create(BackendChildState._secret)
</file context>

@codspeed

codspeed Bot commented Nov 15, 2026

Copy link
Copy Markdown

Merging this PR will degrade performance by 6.13%

2 warnings

⚠️ 16 benchmarks are dominated by system calls

System calls cannot be simulated consistently and are left out of the measure. The reported value understates the real cost. Switch to the Walltime instrument to measure them.

Learn more about system calls

See affected benchmarks

⚠️ 3 benchmarks with a significant change ran on different hardware

Their base and head results were measured on machines with a different CPU, core count or memory, so the changes might not come from the code. Run base and head on the same runner type.

Learn more about Macro Runners

See affected benchmarks

❌ 3 regressed benchmarks
✅ 140 untouched benchmarks
⏩ 8 skipped benchmarks1

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Benchmark BASE HEAD Efficiency
❌ test_route_matching[1000] 92.1 ms 98.9 ms -6.82%
❌ test_route_matching[100] 9.6 ms 10.3 ms -6.77%
❌ test_route_matching[10] 1.3 ms 1.3 ms -4.78%

Tip

Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.


Comparing ShaanGS:fix/backend-var-ui-errors (b042ce2) with main (8e5bb84)

Open in CodSpeed

Footnotes

  1. 8 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports. ↩

This branch has not been deployed

No deployments
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.

Backend var used as a child, typed prop or event argument still gives a cryptic error, and State._x == ... is silently False

1 participant