Browse Source

Update pc.form on_submit args (#1033)

Nikhil Rao 2 years ago
parent
commit
e6a679d3a3

+ 26 - 7
pynecone/components/component.py

@@ -94,6 +94,13 @@ class Component(Base, ABC):
         Raises:
         Raises:
             TypeError: If an invalid prop is passed.
             TypeError: If an invalid prop is passed.
         """
         """
+        # Set the id and children initially.
+        initial_kwargs = {
+            "id": kwargs.get("id"),
+            "children": kwargs.get("children", []),
+        }
+        super().__init__(**initial_kwargs)
+
         # Get the component fields, triggers, and props.
         # Get the component fields, triggers, and props.
         fields = self.get_fields()
         fields = self.get_fields()
         triggers = self.get_triggers()
         triggers = self.get_triggers()
@@ -264,17 +271,15 @@ class Component(Base, ABC):
             events=events, state_name=state_name, full_control=full_control
             events=events, state_name=state_name, full_control=full_control
         )
         )
 
 
-    @classmethod
-    def get_triggers(cls) -> Set[str]:
+    def get_triggers(self) -> Set[str]:
         """Get the event triggers for the component.
         """Get the event triggers for the component.
 
 
         Returns:
         Returns:
             The event triggers.
             The event triggers.
         """
         """
-        return EVENT_TRIGGERS | set(cls.get_controlled_triggers())
+        return EVENT_TRIGGERS | set(self.get_controlled_triggers())
 
 
-    @classmethod
-    def get_controlled_triggers(cls) -> Dict[str, Var]:
+    def get_controlled_triggers(self) -> Dict[str, Var]:
         """Get the event triggers that pass the component's value to the handler.
         """Get the event triggers that pass the component's value to the handler.
 
 
         Returns:
         Returns:
@@ -488,7 +493,7 @@ class Component(Base, ABC):
 
 
         # Add the hook code for the children.
         # Add the hook code for the children.
         for child in self.children:
         for child in self.children:
-            code.update(child.get_hooks())
+            code |= child.get_hooks()
 
 
         return code
         return code
 
 
@@ -502,6 +507,20 @@ class Component(Base, ABC):
             return None
             return None
         return format.format_ref(self.id)
         return format.format_ref(self.id)
 
 
+    def get_refs(self) -> Set[str]:
+        """Get the refs for the children of the component.
+
+        Returns:
+            The refs for the children.
+        """
+        refs = set()
+        ref = self.get_ref()
+        if ref is not None:
+            refs.add(ref)
+        for child in self.children:
+            refs |= child.get_refs()
+        return refs
+
     def get_custom_components(
     def get_custom_components(
         self, seen: Optional[Set[str]] = None
         self, seen: Optional[Set[str]] = None
     ) -> Set[CustomComponent]:
     ) -> Set[CustomComponent]:
@@ -565,7 +584,7 @@ class CustomComponent(Component):
     library = f"/{constants.COMPONENTS_PATH}"
     library = f"/{constants.COMPONENTS_PATH}"
 
 
     # The function that creates the component.
     # The function that creates the component.
-    component_fn: Callable[..., Component]
+    component_fn: Callable[..., Component] = Component.create
 
 
     # The props of the component.
     # The props of the component.
     props: Dict[str, Any] = {}
     props: Dict[str, Any] = {}

+ 1 - 2
pynecone/components/forms/checkbox.py

@@ -48,8 +48,7 @@ class Checkbox(ChakraComponent):
     # The spacing between the checkbox and its label text (0.5rem)
     # The spacing between the checkbox and its label text (0.5rem)
     spacing: Var[str]
     spacing: Var[str]
 
 
-    @classmethod
-    def get_controlled_triggers(cls) -> Dict[str, Var]:
+    def get_controlled_triggers(self) -> Dict[str, Var]:
         """Get the event triggers that pass the component's value to the handler.
         """Get the event triggers that pass the component's value to the handler.
 
 
         Returns:
         Returns:

+ 1 - 2
pynecone/components/forms/copytoclipboard.py

@@ -16,8 +16,7 @@ class CopyToClipboard(Component):
     # The text to copy when clicked.
     # The text to copy when clicked.
     text: Var[str]
     text: Var[str]
 
 
-    @classmethod
-    def get_controlled_triggers(cls) -> Set[str]:
+    def get_controlled_triggers(self) -> Set[str]:
         """Get the event triggers that pass the component's value to the handler.
         """Get the event triggers that pass the component's value to the handler.
 
 
         Returns:
         Returns:

+ 1 - 2
pynecone/components/forms/editable.py

@@ -36,8 +36,7 @@ class Editable(ChakraComponent):
     # The initial value of the Editable in both edit and preview mode.
     # The initial value of the Editable in both edit and preview mode.
     default_value: Var[str]
     default_value: Var[str]
 
 
-    @classmethod
-    def get_controlled_triggers(cls) -> Dict[str, Var]:
+    def get_controlled_triggers(self) -> Dict[str, Var]:
         """Get the event triggers that pass the component's value to the handler.
         """Get the event triggers that pass the component's value to the handler.
 
 
         Returns:
         Returns:

+ 12 - 7
pynecone/components/forms/formcontrol.py

@@ -1,6 +1,6 @@
 """Form components."""
 """Form components."""
 
 
-from typing import Set
+from typing import Dict
 
 
 from pynecone.components.component import Component
 from pynecone.components.component import Component
 from pynecone.components.libs.chakra import ChakraComponent
 from pynecone.components.libs.chakra import ChakraComponent
@@ -14,14 +14,19 @@ class Form(ChakraComponent):
 
 
     as_: Var[str] = "form"  # type: ignore
     as_: Var[str] = "form"  # type: ignore
 
 
-    @classmethod
-    def get_triggers(cls) -> Set[str]:
-        """Get the event triggers for the component.
+    def get_controlled_triggers(self) -> Dict[str, Dict]:
+        """Get the event triggers that pass the component's value to the handler.
 
 
         Returns:
         Returns:
-            The event triggers.
+            A dict mapping the event trigger to the var that is passed to the handler.
         """
         """
-        return super().get_triggers() | {"on_submit"}
+        # Send all the input refs to the handler.
+        return {
+            "on_submit": {
+                ref[4:]: Var.create(f"{ref}.current.value", is_local=False)
+                for ref in self.get_refs()
+            }
+        }
 
 
 
 
 class FormControl(ChakraComponent):
 class FormControl(ChakraComponent):
@@ -52,7 +57,7 @@ class FormControl(ChakraComponent):
         input=None,
         input=None,
         help_text=None,
         help_text=None,
         error_message=None,
         error_message=None,
-        **props
+        **props,
     ) -> Component:
     ) -> Component:
         """Create a form control component.
         """Create a form control component.
 
 

+ 1 - 2
pynecone/components/forms/input.py

@@ -55,8 +55,7 @@ class Input(ChakraComponent):
             {"/utils/state": {ImportVar(tag="set_val")}},
             {"/utils/state": {ImportVar(tag="set_val")}},
         )
         )
 
 
-    @classmethod
-    def get_controlled_triggers(cls) -> Dict[str, Var]:
+    def get_controlled_triggers(self) -> Dict[str, Var]:
         """Get the event triggers that pass the component's value to the handler.
         """Get the event triggers that pass the component's value to the handler.
 
 
         Returns:
         Returns:

+ 1 - 2
pynecone/components/forms/numberinput.py

@@ -64,8 +64,7 @@ class NumberInput(ChakraComponent):
     # "outline" | "filled" | "flushed" | "unstyled"
     # "outline" | "filled" | "flushed" | "unstyled"
     variant: Var[str]
     variant: Var[str]
 
 
-    @classmethod
-    def get_controlled_triggers(cls) -> Dict[str, Var]:
+    def get_controlled_triggers(self) -> Dict[str, Var]:
         """Get the event triggers that pass the component's value to the handler.
         """Get the event triggers that pass the component's value to the handler.
 
 
         Returns:
         Returns:

+ 1 - 2
pynecone/components/forms/pininput.py

@@ -55,8 +55,7 @@ class PinInput(ChakraComponent):
     # "outline" | "flushed" | "filled" | "unstyled"
     # "outline" | "flushed" | "filled" | "unstyled"
     variant: Var[str]
     variant: Var[str]
 
 
-    @classmethod
-    def get_controlled_triggers(cls) -> Dict[str, Var]:
+    def get_controlled_triggers(self) -> Dict[str, Var]:
         """Get the event triggers that pass the component's value to the handler.
         """Get the event triggers that pass the component's value to the handler.
 
 
         Returns:
         Returns:

+ 1 - 2
pynecone/components/forms/radio.py

@@ -23,8 +23,7 @@ class RadioGroup(ChakraComponent):
     # The default value.
     # The default value.
     default_value: Var[Any]
     default_value: Var[Any]
 
 
-    @classmethod
-    def get_controlled_triggers(cls) -> Dict[str, Var]:
+    def get_controlled_triggers(self) -> Dict[str, Var]:
         """Get the event triggers that pass the component's value to the handler.
         """Get the event triggers that pass the component's value to the handler.
 
 
         Returns:
         Returns:

+ 1 - 2
pynecone/components/forms/rangeslider.py

@@ -43,8 +43,7 @@ class RangeSlider(ChakraComponent):
     # The minimum distance between slider thumbs. Useful for preventing the thumbs from being too close together.
     # The minimum distance between slider thumbs. Useful for preventing the thumbs from being too close together.
     min_steps_between_thumbs: Var[int]
     min_steps_between_thumbs: Var[int]
 
 
-    @classmethod
-    def get_controlled_triggers(cls) -> Dict[str, Var]:
+    def get_controlled_triggers(self) -> Dict[str, Var]:
         """Get the event triggers that pass the component's value to the handler.
         """Get the event triggers that pass the component's value to the handler.
 
 
         Returns:
         Returns:

+ 1 - 2
pynecone/components/forms/select.py

@@ -48,8 +48,7 @@ class Select(ChakraComponent):
     # The size of the select.
     # The size of the select.
     size: Var[str]
     size: Var[str]
 
 
-    @classmethod
-    def get_controlled_triggers(cls) -> Dict[str, Var]:
+    def get_controlled_triggers(self) -> Dict[str, Var]:
         """Get the event triggers that pass the component's value to the handler.
         """Get the event triggers that pass the component's value to the handler.
 
 
         Returns:
         Returns:

+ 1 - 2
pynecone/components/forms/slider.py

@@ -61,8 +61,7 @@ class Slider(ChakraComponent):
     # Maximum width of the slider.
     # Maximum width of the slider.
     max_w: Var[str]
     max_w: Var[str]
 
 
-    @classmethod
-    def get_controlled_triggers(cls) -> Dict[str, Var]:
+    def get_controlled_triggers(self) -> Dict[str, Var]:
         """Get the event triggers that pass the component's value to the handler.
         """Get the event triggers that pass the component's value to the handler.
 
 
         Returns:
         Returns:

+ 1 - 2
pynecone/components/forms/switch.py

@@ -41,8 +41,7 @@ class Switch(ChakraComponent):
     # The color scheme of the switch (e.g. "blue", "green", "red", etc.)
     # The color scheme of the switch (e.g. "blue", "green", "red", etc.)
     color_scheme: Var[str]
     color_scheme: Var[str]
 
 
-    @classmethod
-    def get_controlled_triggers(cls) -> Dict[str, Var]:
+    def get_controlled_triggers(self) -> Dict[str, Var]:
         """Get the event triggers that pass the component's value to the handler.
         """Get the event triggers that pass the component's value to the handler.
 
 
         Returns:
         Returns:

+ 1 - 2
pynecone/components/forms/textarea.py

@@ -42,8 +42,7 @@ class TextArea(ChakraComponent):
     # "outline" | "filled" | "flushed" | "unstyled"
     # "outline" | "filled" | "flushed" | "unstyled"
     variant: Var[str]
     variant: Var[str]
 
 
-    @classmethod
-    def get_controlled_triggers(cls) -> Dict[str, Var]:
+    def get_controlled_triggers(self) -> Dict[str, Var]:
         """Get the event triggers that pass the component's value to the handler.
         """Get the event triggers that pass the component's value to the handler.
 
 
         Returns:
         Returns:

+ 1 - 2
pynecone/components/forms/upload.py

@@ -80,8 +80,7 @@ class Upload(Component):
         # Create the component.
         # Create the component.
         return super().create(zone, on_drop=upload_file, **upload_props)
         return super().create(zone, on_drop=upload_file, **upload_props)
 
 
-    @classmethod
-    def get_controlled_triggers(cls) -> Dict[str, Var]:
+    def get_controlled_triggers(self) -> Dict[str, Var]:
         """Get the event triggers that pass the component's value to the handler.
         """Get the event triggers that pass the component's value to the handler.
 
 
         Returns:
         Returns:

+ 2 - 2
pynecone/components/layout/cond.py

@@ -17,10 +17,10 @@ class Cond(Component):
     cond: Var[Any]
     cond: Var[Any]
 
 
     # The component to render if the cond is true.
     # The component to render if the cond is true.
-    comp1: Component
+    comp1: Component = Fragment.create()
 
 
     # The component to render if the cond is false.
     # The component to render if the cond is false.
-    comp2: Component
+    comp2: Component = Fragment.create()
 
 
     @classmethod
     @classmethod
     def create(
     def create(

+ 2 - 1
pynecone/components/layout/foreach.py

@@ -4,6 +4,7 @@ from __future__ import annotations
 from typing import Any, Callable, List
 from typing import Any, Callable, List
 
 
 from pynecone.components.component import Component
 from pynecone.components.component import Component
+from pynecone.components.layout.fragment import Fragment
 from pynecone.components.tags import IterTag
 from pynecone.components.tags import IterTag
 from pynecone.vars import BaseVar, Var, get_unique_variable_name
 from pynecone.vars import BaseVar, Var, get_unique_variable_name
 
 
@@ -15,7 +16,7 @@ class Foreach(Component):
     iterable: Var[List]
     iterable: Var[List]
 
 
     # A function from the render args to the component.
     # A function from the render args to the component.
-    render_fn: Callable
+    render_fn: Callable = Fragment.create
 
 
     @classmethod
     @classmethod
     def create(cls, iterable: Var[List], render_fn: Callable, **props) -> Foreach:
     def create(cls, iterable: Var[List], render_fn: Callable, **props) -> Foreach:

+ 1 - 2
pynecone/components/media/avatar.py

@@ -35,8 +35,7 @@ class Avatar(ChakraComponent):
     # "2xs" | "xs" | "sm" | "md" | "lg" | "xl" | "2xl" | "full"
     # "2xs" | "xs" | "sm" | "md" | "lg" | "xl" | "2xl" | "full"
     size: Var[str]
     size: Var[str]
 
 
-    @classmethod
-    def get_triggers(cls) -> Set[str]:
+    def get_triggers(self) -> Set[str]:
         """Get the event triggers for the component.
         """Get the event triggers for the component.
 
 
         Returns:
         Returns:

+ 1 - 2
pynecone/components/media/image.py

@@ -43,8 +43,7 @@ class Image(ChakraComponent):
     # The image srcset attribute.
     # The image srcset attribute.
     src_set: Var[str]
     src_set: Var[str]
 
 
-    @classmethod
-    def get_triggers(cls) -> Set[str]:
+    def get_triggers(self) -> Set[str]:
         """Get the event triggers for the component.
         """Get the event triggers for the component.
 
 
         Returns:
         Returns:

+ 1 - 2
pynecone/components/overlay/alertdialog.py

@@ -52,8 +52,7 @@ class AlertDialog(ChakraComponent):
     # If true, the siblings of the modal will have `aria-hidden` set to true so that screen readers can only see the modal. This is commonly known as making the other elements **inert**
     # If true, the siblings of the modal will have `aria-hidden` set to true so that screen readers can only see the modal. This is commonly known as making the other elements **inert**
     use_inert: Var[bool]
     use_inert: Var[bool]
 
 
-    @classmethod
-    def get_triggers(cls) -> Set[str]:
+    def get_triggers(self) -> Set[str]:
         """Get the event triggers for the component.
         """Get the event triggers for the component.
 
 
         Returns:
         Returns:

+ 1 - 2
pynecone/components/overlay/drawer.py

@@ -58,8 +58,7 @@ class Drawer(ChakraComponent):
     # Variant of drawer
     # Variant of drawer
     variant: Var[str]
     variant: Var[str]
 
 
-    @classmethod
-    def get_triggers(cls) -> Set[str]:
+    def get_triggers(self) -> Set[str]:
         """Get the event triggers for the component.
         """Get the event triggers for the component.
 
 
         Returns:
         Returns:

+ 1 - 2
pynecone/components/overlay/menu.py

@@ -60,8 +60,7 @@ class Menu(ChakraComponent):
     # The CSS positioning strategy to use. ("fixed" | "absolute")
     # The CSS positioning strategy to use. ("fixed" | "absolute")
     strategy: Var[str]
     strategy: Var[str]
 
 
-    @classmethod
-    def get_triggers(cls) -> Set[str]:
+    def get_triggers(self) -> Set[str]:
         """Get the event triggers for the component.
         """Get the event triggers for the component.
 
 
         Returns:
         Returns:

+ 1 - 2
pynecone/components/overlay/modal.py

@@ -52,8 +52,7 @@ class Modal(ChakraComponent):
     # A11y: If true, the siblings of the modal will have `aria-hidden` set to true so that screen readers can only see the modal. This is commonly known as making the other elements **inert**
     # A11y: If true, the siblings of the modal will have `aria-hidden` set to true so that screen readers can only see the modal. This is commonly known as making the other elements **inert**
     use_inert: Var[bool]
     use_inert: Var[bool]
 
 
-    @classmethod
-    def get_triggers(cls) -> Set[str]:
+    def get_triggers(self) -> Set[str]:
         """Get the event triggers for the component.
         """Get the event triggers for the component.
 
 
         Returns:
         Returns:

+ 1 - 2
pynecone/components/overlay/popover.py

@@ -75,8 +75,7 @@ class Popover(ChakraComponent):
     # The interaction that triggers the popover. hover - means the popover will open when you hover with mouse or focus with keyboard on the popover trigger click - means the popover will open on click or press Enter to Space on keyboard ("click" | "hover")
     # The interaction that triggers the popover. hover - means the popover will open when you hover with mouse or focus with keyboard on the popover trigger click - means the popover will open on click or press Enter to Space on keyboard ("click" | "hover")
     trigger: Var[str]
     trigger: Var[str]
 
 
-    @classmethod
-    def get_triggers(cls) -> Set[str]:
+    def get_triggers(self) -> Set[str]:
         """Get the event triggers for the component.
         """Get the event triggers for the component.
 
 
         Returns:
         Returns:

+ 1 - 2
pynecone/components/overlay/tooltip.py

@@ -62,8 +62,7 @@ class Tooltip(ChakraComponent):
     # If true, the tooltip will wrap its children in a `<span/>` with `tabIndex=0`
     # If true, the tooltip will wrap its children in a `<span/>` with `tabIndex=0`
     should_wrap_children: Var[bool]
     should_wrap_children: Var[bool]
 
 
-    @classmethod
-    def get_triggers(cls) -> Set[str]:
+    def get_triggers(self) -> Set[str]:
         """Get the event triggers for the component.
         """Get the event triggers for the component.
 
 
         Returns:
         Returns:

+ 3 - 13
pynecone/components/tags/tag.py

@@ -3,7 +3,6 @@
 from __future__ import annotations
 from __future__ import annotations
 
 
 import json
 import json
-import re
 from typing import TYPE_CHECKING, Any, Dict, List, Optional, Set, Tuple, Union
 from typing import TYPE_CHECKING, Any, Dict, List, Optional, Set, Tuple, Union
 
 
 from plotly.graph_objects import Figure
 from plotly.graph_objects import Figure
@@ -97,14 +96,10 @@ class Tag(Base):
             prop = json.loads(to_json(prop))["data"]  # type: ignore
             prop = json.loads(to_json(prop))["data"]  # type: ignore
 
 
         # For dictionaries, convert any properties to strings.
         # For dictionaries, convert any properties to strings.
-        else:
-            if isinstance(prop, dict):
-                # Convert any var keys to strings.
-                prop = {
-                    key: str(val) if isinstance(val, Var) else val
-                    for key, val in prop.items()
-                }
+        elif isinstance(prop, dict):
+            prop = format.format_dict(prop)
 
 
+        else:
             # Dump the prop as JSON.
             # Dump the prop as JSON.
             try:
             try:
                 prop = format.json_dumps(prop)
                 prop = format.json_dumps(prop)
@@ -113,11 +108,6 @@ class Tag(Base):
                     f"Could not format prop: {prop} of type {type(prop)}"
                     f"Could not format prop: {prop} of type {type(prop)}"
                 ) from e
                 ) from e
 
 
-            # This substitution is necessary to unwrap var values.
-            prop = re.sub('"{', "", prop)
-            prop = re.sub('}"', "", prop)
-            prop = re.sub('\\\\"', '"', prop)
-
         # Wrap the variable in braces.
         # Wrap the variable in braces.
         assert isinstance(prop, str), "The prop must be a string."
         assert isinstance(prop, str), "The prop must be a string."
         return format.wrap(prop, "{", check_first=False)
         return format.wrap(prop, "{", check_first=False)

+ 28 - 0
pynecone/utils/format.py

@@ -15,6 +15,7 @@ from pynecone import constants
 from pynecone.utils import types
 from pynecone.utils import types
 
 
 if TYPE_CHECKING:
 if TYPE_CHECKING:
+    from pynecone.components.component import ComponentStyle
     from pynecone.event import EventChain, EventHandler, EventSpec
     from pynecone.event import EventChain, EventHandler, EventSpec
 
 
 WRAP_MAP = {
 WRAP_MAP = {
@@ -411,6 +412,33 @@ def format_ref(ref: str) -> str:
     return f"ref_{clean_ref}"
     return f"ref_{clean_ref}"
 
 
 
 
+def format_dict(prop: ComponentStyle) -> str:
+    """Format a dict with vars potentially as values.
+
+    Args:
+        prop: The dict to format.
+
+    Returns:
+        The formatted dict.
+    """
+    # Import here to avoid circular imports.
+    from pynecone.vars import Var
+
+    # Convert any var keys to strings.
+    prop = {key: str(val) if isinstance(val, Var) else val for key, val in prop.items()}
+
+    # Dump the dict to a string.
+    fprop = json_dumps(prop)
+
+    # This substitution is necessary to unwrap var values.
+    fprop = re.sub('"{', "", fprop)
+    fprop = re.sub('}"', "", fprop)
+    fprop = re.sub('\\\\"', '"', fprop)
+
+    # Return the formatted dict.
+    return fprop
+
+
 def json_dumps(obj: Any) -> str:
 def json_dumps(obj: Any) -> str:
     """Takes an object and returns a jsonified string.
     """Takes an object and returns a jsonified string.
 
 

+ 3 - 0
pynecone/vars.py

@@ -101,6 +101,9 @@ class Var(ABC):
             value = json.loads(to_json(value))["data"]  # type: ignore
             value = json.loads(to_json(value))["data"]  # type: ignore
             type_ = Figure
             type_ = Figure
 
 
+        if isinstance(value, dict):
+            value = format.format_dict(value)
+
         try:
         try:
             name = value if isinstance(value, str) else json.dumps(value)
             name = value if isinstance(value, str) else json.dumps(value)
         except TypeError as e:
         except TypeError as e:

+ 5 - 6
tests/components/test_component.py

@@ -62,8 +62,7 @@ def component2() -> Type[Component]:
         # A test list prop.
         # A test list prop.
         arr: Var[List[str]]
         arr: Var[List[str]]
 
 
-        @classmethod
-        def get_controlled_triggers(cls) -> Dict[str, Var]:
+        def get_controlled_triggers(self) -> Dict[str, Var]:
             """Test controlled triggers.
             """Test controlled triggers.
 
 
             Returns:
             Returns:
@@ -307,8 +306,8 @@ def test_get_controlled_triggers(component1, component2):
         component1: A test component.
         component1: A test component.
         component2: A test component.
         component2: A test component.
     """
     """
-    assert component1.get_controlled_triggers() == dict()
-    assert set(component2.get_controlled_triggers()) == {"on_open", "on_close"}
+    assert component1().get_controlled_triggers() == dict()
+    assert set(component2().get_controlled_triggers()) == {"on_open", "on_close"}
 
 
 
 
 def test_get_triggers(component1, component2):
 def test_get_triggers(component1, component2):
@@ -318,8 +317,8 @@ def test_get_triggers(component1, component2):
         component1: A test component.
         component1: A test component.
         component2: A test component.
         component2: A test component.
     """
     """
-    assert component1.get_triggers() == EVENT_TRIGGERS
-    assert component2.get_triggers() == {"on_open", "on_close"} | EVENT_TRIGGERS
+    assert component1().get_triggers() == EVENT_TRIGGERS
+    assert component2().get_triggers() == {"on_open", "on_close"} | EVENT_TRIGGERS
 
 
 
 
 def test_create_custom_component(my_component):
 def test_create_custom_component(my_component):