Selaa lähdekoodia

Allow fstrings as component children (#890)

Nikhil Rao 2 vuotta sitten
vanhempi
säilyke
f0346506d7

+ 1 - 0
pynecone/app.py

@@ -263,6 +263,7 @@ class App(Base):
         # Generate the component if it is a callable.
         try:
             component = component if isinstance(component, Component) else component()
+            component.set_state(self.state)
         except TypeError as e:
             message = str(e)
             if "BaseVar" in message or "ComputedVar" in message:

+ 5 - 1
pynecone/components/base/bare.py

@@ -27,4 +27,8 @@ class Bare(Component):
         return cls(contents=str(contents))  # type: ignore
 
     def _render(self) -> Tag:
-        return Tagless(contents=str(self.contents))
+        contents = str(self.contents)
+        if self.state is not None:
+            check = f"{{{self.state.get_name()}"
+            contents = str(self.contents).replace(check, f"${check}")
+        return Tagless(contents=contents)

+ 19 - 0
pynecone/components/component.py

@@ -24,6 +24,9 @@ from pynecone.style import Style
 from pynecone.utils import format, imports, path_ops, types
 from pynecone.var import BaseVar, Var
 
+if typing.TYPE_CHECKING:
+    from pynecone.state import State
+
 
 class Component(Base, ABC):
     """The base class for all Pynecone components."""
@@ -34,6 +37,9 @@ class Component(Base, ABC):
     # The style of the component.
     style: Style = Style()
 
+    # The app state the component is connected to.
+    state: Optional[Type[State]] = None
+
     # A mapping from event triggers to event chains.
     event_triggers: Dict[str, Union[EventChain, Var]] = {}
 
@@ -393,6 +399,19 @@ class Component(Base, ABC):
             child.add_style(style)
         return self
 
+    def set_state(self, state: Type[State]):
+        """Set the state of the component and its children.
+
+        Args:
+            state: The state to set.
+        """
+        # Set the state of the component.
+        self.state = state
+
+        # Set the state of the children.
+        for child in self.children:
+            child.set_state(state)
+
     def render(self) -> str:
         """Render the component.
 

+ 0 - 0
tests/components/base/__init__.py


+ 25 - 0
tests/components/base/test_bare.py

@@ -0,0 +1,25 @@
+import pytest
+
+from pynecone.components.base.bare import Bare
+from pynecone.state import DefaultState
+
+
+@pytest.mark.parametrize(
+    "contents,expected",
+    [
+        ("hello", "hello"),
+        ("{}", "{}"),
+        ("{default_state.name}", "${default_state.name}"),
+        ("{state.name}", "{state.name}"),
+    ],
+)
+def test_fstrings(contents, expected):
+    """Test that fstrings are rendered correctly.
+
+    Args:
+        contents: The contents of the component.
+        expected: The expected output.
+    """
+    comp = Bare.create(contents)
+    comp.set_state(DefaultState)
+    assert str(comp) == expected

+ 25 - 1
tests/components/test_component.py

@@ -6,7 +6,7 @@ import pynecone as pc
 from pynecone.components.component import Component, CustomComponent, custom_component
 from pynecone.components.layout.box import Box
 from pynecone.event import EVENT_ARG, EVENT_TRIGGERS, EventHandler
-from pynecone.state import State
+from pynecone.state import DefaultState, State
 from pynecone.style import Style
 from pynecone.utils import imports
 from pynecone.var import Var
@@ -434,3 +434,27 @@ def test_get_hooks_nested2(component3, component4):
         ).get_hooks()
         == exp_hooks
     )
+
+
+def test_set_state(component1, component2, component3):
+    """Test that setting the state of a component works.
+
+    Args:
+        component1: test component.
+        component2: another component.
+        component3: component with hooks defined.
+    """
+    c2 = component2.create()
+    c3 = component3.create()
+    c1 = component1.create(c2, c3)
+
+    # State should be None by default.
+    assert c1.state is None
+    assert c2.state is None
+    assert c3.state is None
+
+    # Setting the parent state should set the child state.
+    c1.set_state(DefaultState)
+    assert c1.state == DefaultState
+    assert c2.state == DefaultState
+    assert c3.state == DefaultState