diff --git a/reflex/components/tags/tag.py b/reflex/components/tags/tag.py
index 39f347196..855b13056 100644
--- a/reflex/components/tags/tag.py
+++ b/reflex/components/tags/tag.py
@@ -2,20 +2,13 @@
from __future__ import annotations
-import json
-from typing import TYPE_CHECKING, Any, Dict, List, Optional, Set, Tuple, Union
-
-from plotly.graph_objects import Figure
-from plotly.io import to_json
+from typing import Any, Dict, List, Optional, Set, Tuple, Union
from reflex.base import Base
-from reflex.event import EVENT_ARG, EventChain
+from reflex.event import EventChain
from reflex.utils import format, types
from reflex.vars import Var
-if TYPE_CHECKING:
- from reflex.components.component import ComponentStyle
-
class Tag(Base):
"""A React tag."""
@@ -52,61 +45,6 @@ class Tag(Base):
}
super().__init__(*args, **kwargs)
- @staticmethod
- def format_prop(
- prop: Union[Var, EventChain, ComponentStyle, str],
- ) -> Union[int, float, str]:
- """Format a prop.
-
- Args:
- prop: The prop to format.
-
- Returns:
- The formatted prop to display within a tag.
-
- Raises:
- TypeError: If the prop is not a valid type.
- """
- try:
- # Handle var props.
- if isinstance(prop, Var):
- if not prop.is_local or prop.is_string:
- return str(prop)
- if types._issubclass(prop.type_, str):
- return format.format_string(prop.full_name)
- prop = prop.full_name
-
- # Handle event props.
- elif isinstance(prop, EventChain):
- chain = ",".join([format.format_event(event) for event in prop.events])
- event = f"Event([{chain}], {EVENT_ARG})"
- prop = f"{EVENT_ARG} => {event}"
-
- # Handle other types.
- elif isinstance(prop, str):
- if format.is_wrapped(prop, "{"):
- return prop
- return format.json_dumps(prop)
-
- elif isinstance(prop, Figure):
- prop = json.loads(to_json(prop))["data"] # type: ignore
-
- # For dictionaries, convert any properties to strings.
- elif isinstance(prop, dict):
- prop = format.format_dict(prop)
-
- else:
- # Dump the prop as JSON.
- prop = format.json_dumps(prop)
- except TypeError as e:
- raise TypeError(
- f"Could not format prop: {prop} of type {type(prop)}"
- ) from e
-
- # Wrap the variable in braces.
- assert isinstance(prop, str), "The prop must be a string."
- return format.wrap(prop, "{", check_first=False)
-
def format_props(self) -> List:
"""Format the tag's props.
@@ -119,7 +57,7 @@ class Tag(Base):
# Format all the props.
return [
- f"{name}={self.format_prop(prop)}"
+ f"{name}={format.format_prop(prop)}"
for name, prop in sorted(self.props.items())
if prop is not None
] + [str(prop) for prop in self.special_props]
diff --git a/reflex/utils/format.py b/reflex/utils/format.py
index b799b8a06..24eec9a83 100644
--- a/reflex/utils/format.py
+++ b/reflex/utils/format.py
@@ -9,9 +9,10 @@ import os
import os.path as op
import re
import sys
-from typing import TYPE_CHECKING, Any, Type
+from typing import TYPE_CHECKING, Any, Type, Union
import plotly.graph_objects as go
+from plotly.graph_objects import Figure
from plotly.io import to_json
from reflex import constants
@@ -258,6 +259,62 @@ def format_cond(
return wrap(f"{cond} ? {true_value} : {false_value}", "{")
+def format_prop(
+ prop: Union[Var, EventChain, ComponentStyle, str],
+) -> Union[int, float, str]:
+ """Format a prop.
+
+ Args:
+ prop: The prop to format.
+
+ Returns:
+ The formatted prop to display within a tag.
+
+ Raises:
+ TypeError: If the prop is not a valid type.
+ """
+ # import here to avoid circular import.
+ from reflex.event import EVENT_ARG, EventChain
+
+ try:
+ # Handle var props.
+ if isinstance(prop, Var):
+ if not prop.is_local or prop.is_string:
+ return str(prop)
+ if types._issubclass(prop.type_, str):
+ return format_string(prop.full_name)
+ prop = prop.full_name
+
+ # Handle event props.
+ elif isinstance(prop, EventChain):
+ chain = ",".join([format_event(event) for event in prop.events])
+ event = f"Event([{chain}], {EVENT_ARG})"
+ prop = f"{EVENT_ARG} => {event}"
+
+ # Handle other types.
+ elif isinstance(prop, str):
+ if is_wrapped(prop, "{"):
+ return prop
+ return json_dumps(prop)
+
+ elif isinstance(prop, Figure):
+ prop = json.loads(to_json(prop))["data"] # type: ignore
+
+ # For dictionaries, convert any properties to strings.
+ elif isinstance(prop, dict):
+ prop = format_dict(prop)
+
+ else:
+ # Dump the prop as JSON.
+ prop = json_dumps(prop)
+ except TypeError as e:
+ raise TypeError(f"Could not format prop: {prop} of type {type(prop)}") from e
+
+ # Wrap the variable in braces.
+ assert isinstance(prop, str), "The prop must be a string."
+ return wrap(prop, "{", check_first=False)
+
+
def get_event_handler_parts(handler: EventHandler) -> tuple[str, str]:
"""Get the state and function name of an event handler.
diff --git a/tests/components/test_tag.py b/tests/components/test_tag.py
index 3f47891e6..7964838ad 100644
--- a/tests/components/test_tag.py
+++ b/tests/components/test_tag.py
@@ -1,90 +1,11 @@
-from typing import Any, Dict, List
+from typing import Dict, List
import pytest
from reflex.components.tags import CondTag, Tag, tagless
-from reflex.event import EVENT_ARG, EventChain, EventHandler, EventSpec
-from reflex.style import Style
from reflex.vars import BaseVar, Var
-def mock_event(arg):
- pass
-
-
-@pytest.mark.parametrize(
- "prop,formatted",
- [
- ("string", '"string"'),
- ("{wrapped_string}", "{wrapped_string}"),
- (True, "{true}"),
- (False, "{false}"),
- (123, "{123}"),
- (3.14, "{3.14}"),
- ([1, 2, 3], "{[1, 2, 3]}"),
- (["a", "b", "c"], '{["a", "b", "c"]}'),
- ({"a": 1, "b": 2, "c": 3}, '{{"a": 1, "b": 2, "c": 3}}'),
- ({"a": 'foo "bar" baz'}, r'{{"a": "foo \"bar\" baz"}}'),
- (
- {
- "a": 'foo "{ "bar" }" baz',
- "b": BaseVar(name="val", type_="str"),
- },
- r'{{"a": "foo \"{ \"bar\" }\" baz", "b": val}}',
- ),
- (
- EventChain(events=[EventSpec(handler=EventHandler(fn=mock_event))]),
- '{_e => Event([E("mock_event", {})], _e)}',
- ),
- (
- EventChain(
- events=[
- EventSpec(
- handler=EventHandler(fn=mock_event),
- args=((Var.create_safe("arg"), EVENT_ARG.target.value),),
- )
- ]
- ),
- '{_e => Event([E("mock_event", {arg:_e.target.value})], _e)}',
- ),
- ({"a": "red", "b": "blue"}, '{{"a": "red", "b": "blue"}}'),
- (BaseVar(name="var", type_="int"), "{var}"),
- (
- BaseVar(
- name="_",
- type_=Any,
- state="",
- is_local=True,
- is_string=False,
- ),
- "{_}",
- ),
- (BaseVar(name='state.colors["a"]', type_="str"), '{state.colors["a"]}'),
- ({"a": BaseVar(name="val", type_="str")}, '{{"a": val}}'),
- ({"a": BaseVar(name='"val"', type_="str")}, '{{"a": "val"}}'),
- (
- {"a": BaseVar(name='state.colors["val"]', type_="str")},
- '{{"a": state.colors["val"]}}',
- ),
- # tricky real-world case from markdown component
- (
- {
- "h1": f"{{({{node, ...props}}) => }}"
- },
- '{{"h1": ({node, ...props}) => }}',
- ),
- ],
-)
-def test_format_prop(prop: Var, formatted: str):
- """Test that the formatted value of an prop is correct.
-
- Args:
- prop: The prop to test.
- formatted: The expected formatted value.
- """
- assert Tag.format_prop(prop) == formatted
-
-
@pytest.mark.parametrize(
"props,test_props",
[
diff --git a/tests/test_utils.py b/tests/test_utils.py
index c93b7f1b8..5b16af198 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -9,6 +9,9 @@ from packaging import version
from reflex import constants
from reflex.base import Base
+from reflex.components.tags import Tag
+from reflex.event import EVENT_ARG, EventChain, EventHandler, EventSpec
+from reflex.style import Style
from reflex.utils import (
build,
format,
@@ -17,7 +20,11 @@ from reflex.utils import (
types,
)
from reflex.utils import exec as utils_exec
-from reflex.vars import Var
+from reflex.vars import BaseVar, Var
+
+
+def mock_event(arg):
+ pass
def get_above_max_version():
@@ -262,6 +269,79 @@ def test_format_route(route: str, expected: bool):
assert format.format_route(route) == expected
+@pytest.mark.parametrize(
+ "prop,formatted",
+ [
+ ("string", '"string"'),
+ ("{wrapped_string}", "{wrapped_string}"),
+ (True, "{true}"),
+ (False, "{false}"),
+ (123, "{123}"),
+ (3.14, "{3.14}"),
+ ([1, 2, 3], "{[1, 2, 3]}"),
+ (["a", "b", "c"], '{["a", "b", "c"]}'),
+ ({"a": 1, "b": 2, "c": 3}, '{{"a": 1, "b": 2, "c": 3}}'),
+ ({"a": 'foo "bar" baz'}, r'{{"a": "foo \"bar\" baz"}}'),
+ (
+ {
+ "a": 'foo "{ "bar" }" baz',
+ "b": BaseVar(name="val", type_="str"),
+ },
+ r'{{"a": "foo \"{ \"bar\" }\" baz", "b": val}}',
+ ),
+ (
+ EventChain(events=[EventSpec(handler=EventHandler(fn=mock_event))]),
+ '{_e => Event([E("mock_event", {})], _e)}',
+ ),
+ (
+ EventChain(
+ events=[
+ EventSpec(
+ handler=EventHandler(fn=mock_event),
+ args=((Var.create_safe("arg"), EVENT_ARG.target.value),),
+ )
+ ]
+ ),
+ '{_e => Event([E("mock_event", {arg:_e.target.value})], _e)}',
+ ),
+ ({"a": "red", "b": "blue"}, '{{"a": "red", "b": "blue"}}'),
+ (BaseVar(name="var", type_="int"), "{var}"),
+ (
+ BaseVar(
+ name="_",
+ type_=Any,
+ state="",
+ is_local=True,
+ is_string=False,
+ ),
+ "{_}",
+ ),
+ (BaseVar(name='state.colors["a"]', type_="str"), '{state.colors["a"]}'),
+ ({"a": BaseVar(name="val", type_="str")}, '{{"a": val}}'),
+ ({"a": BaseVar(name='"val"', type_="str")}, '{{"a": "val"}}'),
+ (
+ {"a": BaseVar(name='state.colors["val"]', type_="str")},
+ '{{"a": state.colors["val"]}}',
+ ),
+ # tricky real-world case from markdown component
+ (
+ {
+ "h1": f"{{({{node, ...props}}) => }}"
+ },
+ '{{"h1": ({node, ...props}) => }}',
+ ),
+ ],
+)
+def test_format_prop(prop: Var, formatted: str):
+ """Test that the formatted value of an prop is correct.
+
+ Args:
+ prop: The prop to test.
+ formatted: The expected formatted value.
+ """
+ assert format.format_prop(prop) == formatted
+
+
def test_validate_invalid_bun_path(mocker):
"""Test that an error is thrown when a custom specified bun path is not valid
or does not exist.