Repository: reflex-dev/reflex
Pull request: Resolve #304: Relax type requirement on pc.cond
Resolve #304: Relax type requirement on pc.cond

Problem statement:
Relax type requirement on pc.cond
Currently the first argument to `pc.cond` must be a `pc.Var` with type `bool`. However since JS is lax about converting anything to a bool, perhaps we can relax this requirement and just allow anything in.

There's a tradeoff here when we do this stuff implicitly as it may lead to unexpected errors. But if it eases development could be worth it.

Hints from issue discussion:

Commit message: unit test for pc.cond validation

Patch to review:
diff --git a/pynecone/components/layout/cond.py b/pynecone/components/layout/cond.py
index 8146e2d5a33..d80f306f253 100644
--- a/pynecone/components/layout/cond.py
+++ b/pynecone/components/layout/cond.py
@@ -1,9 +1,7 @@
 """Create a list of components from an iterable."""
 from __future__ import annotations
 
-from typing import Optional
-
-import pydantic
+from typing import Any, Optional
 
 from pynecone.components.component import Component
 from pynecone.components.layout.fragment import Fragment
@@ -15,7 +13,7 @@ class Cond(Component):
     """Render one of two components based on a condition."""
 
     # The cond to determine which component to render.
-    cond: Var[bool]
+    cond: Var[Any]
 
     # The component to render if the cond is true.
     comp1: Component
@@ -26,19 +24,6 @@ class Cond(Component):
     # Whether the cond is within another cond.
     is_nested: bool = False
 
-    @pydantic.validator("cond")
-    def validate_cond(cls, cond: Var) -> Var:
-        """Validate that the cond is a boolean.
-
-        Args:
-            cond: The cond to validate.
-
-        Returns:
-            The validated cond.
-        """
-        assert issubclass(cond.type_, bool), "The var must be a boolean."
-        return cond
-
     @classmethod
     def create(
         cls, cond: Var, comp1: Component, comp2: Optional[Component] = None
diff --git a/pynecone/components/tags/cond_tag.py b/pynecone/components/tags/cond_tag.py
index 5aa5e511797..d11f9a7804b 100644
--- a/pynecone/components/tags/cond_tag.py
+++ b/pynecone/components/tags/cond_tag.py
@@ -1,5 +1,7 @@
 """Tag to conditionally render components."""
 
+from typing import Any
+
 from pynecone import utils
 from pynecone.components.tags.tag import Tag
 from pynecone.var import Var
@@ -9,7 +11,7 @@ class CondTag(Tag):
     """A conditional tag."""
 
     # The condition to determine which component to render.
-    cond: Var[bool]
+    cond: Var[Any]
 
     # The code to render if the condition is true.
     true_value: str
diff --git a/tests/components/layout/test_cond.py b/tests/components/layout/test_cond.py
new file mode 100644
index 00000000000..eb1d61e5819
--- /dev/null
+++ b/tests/components/layout/test_cond.py
@@ -0,0 +1,37 @@
+import pytest
+
+import pynecone as pc
+
+
+@pytest.fixture
+def cond_state(request):
+    class CondState(pc.State):
+        value: request.param["value_type"] = request.param["value"]
+
+    return CondState
+
+
+@pytest.mark.parametrize(
+    "cond_state",
+    [
+        pytest.param({"value_type": bool, "value": True}),
+        pytest.param({"value_type": int, "value": 0}),
+        pytest.param({"value_type": str, "value": "true"}),
+    ],
+    indirect=True,
+)
+def test_validate_cond(cond_state: pc.Var):
+    """Test if cond can be a pc.Val with any values"""
+    cond_component = pc.cond(
+        cond_state.value,
+        pc.text("cond is True"),
+        pc.text("cond is False"),
+    )
+
+    rendered_cond = cond_component._render()
+
+    assert str(rendered_cond) == (
+        "{cond_state.value ? "
+        "<Text>{`cond is True`}</Text> : "
+        "<Text>{`cond is False`}</Text>}"
+    )


Code review comments:
diff_hunk: @@ -0,0 +1,37 @@
+import pytest
+
+import pynecone as pc
+
+
+@pytest.fixture
+def cond_state(request):
+    class CondState(pc.State):
+        value: request.param["value_type"] = request.param["value"]
+
+    return CondState
+
+
+@pytest.mark.parametrize(
+    "cond_state",
+    [
+        pytest.param({"value_type": bool, "value": True}),
+        pytest.param({"value_type": int, "value": 0}),
+        pytest.param({"value_type": str, "value": "true"}),
+    ],
+    indirect=True,
+)
+def test_validate_cond(cond_state: pc.Var):
+    """Test if cond can be a pc.Val with any values"""
+    cond_component = pc.cond(
+        cond_state.value,
+        pc.text("cond is True"),
+        pc.text("cond is False"),
+    )
path: tests/components/layout/test_cond.py
text: @author:
pyright is complaining `error: Module is not callable`. Any thoughts?

@user1:
This may because we do some magic to create the `pc.` functions. In the tests maybe we can try to import the components directly:

```python
from pynecone.components.typography.text import Text
from pynecone.components.layout.cond import Cond
```

Then call the create method explicitly:

```python
cond_component = Cond.create(
    cond_state.value,
    Text.create("cond is True"),
    Text.create("cond is False"),
)
```

@author:
Thanks. It seems to fix it.

> This may because we do some magic to create the pc. functions.

I am curious how we do this? e.g. I don't see any `def cond():` for `pc.cond`

@user1:
[This is the line](https://github.com/pynecone-io/pynecone/blob/main/pynecone/components/__init__.py#L20) that does the magic: we create `pc.` functions that map to each component's `create` method.