浏览代码

Separate `get_hooks` and `get_hooks_internal` for stable output (#2710)

* Separate `get_hooks` and `get_hooks_internal` for stable output

When downstream component wrappers depend on State when writing hooks, they
need to be assured that all internal hooks (events, var hooks, memoized
handlers, etc) will be rendered prior to user-defined hooks.

This also makes it less likely for downstream components to feel the need to
overwrite `get_hooks` (no underscore) directly and break internal functioning
of Reflex components.

* Include internal hooks in AppWrap and Page

* Apply get_hooks_internal in a few more places
Masen Furer 1 年之前
父节点
当前提交
b89a18f632

+ 5 - 1
reflex/.templates/jinja/web/pages/stateful_component.js.jinja2

@@ -1,7 +1,7 @@
 {% import 'web/pages/utils.js.jinja2' as utils %}
 
 export function {{tag_name}} () {
-  {% for hook in component.get_hooks() %}
+  {% for hook in component.get_hooks_internal() %}
   {{ hook }}
   {% endfor %}
 
@@ -9,6 +9,10 @@ export function {{tag_name}} () {
   {{ hook }}
   {% endfor %}
 
+  {% for hook in component.get_hooks() %}
+  {{ hook }}
+  {% endfor %}
+
   return (
     {{utils.render(component.render(), indent_width=0)}}
   )

+ 2 - 2
reflex/compiler/compiler.py

@@ -50,7 +50,7 @@ def _compile_app(app_root: Component) -> str:
     return templates.APP_ROOT.render(
         imports=utils.compile_imports(app_root.get_imports()),
         custom_codes=app_root.get_custom_code(),
-        hooks=app_root.get_hooks(),
+        hooks=app_root.get_hooks_internal() | app_root.get_hooks(),
         render=app_root.render(),
     )
 
@@ -119,7 +119,7 @@ def _compile_page(
         imports=imports,
         dynamic_imports=component.get_dynamic_imports(),
         custom_codes=component.get_custom_code(),
-        hooks=component.get_hooks(),
+        hooks=component.get_hooks_internal() | component.get_hooks(),
         render=component.render(),
         **kwargs,
     )

+ 1 - 1
reflex/compiler/utils.py

@@ -256,7 +256,7 @@ def compile_custom_component(
             "name": component.tag,
             "props": props,
             "render": render.render(),
-            "hooks": render.get_hooks(),
+            "hooks": render.get_hooks_internal() | render.get_hooks(),
             "custom_code": render.get_custom_code(),
         },
         imports,

+ 33 - 3
reflex/components/component.py

@@ -74,6 +74,14 @@ class BaseComponent(Base, ABC):
             The dictionary for template of the component.
         """
 
+    @abstractmethod
+    def get_hooks_internal(self) -> set[str]:
+        """Get the reflex internal hooks for the component and its children.
+
+        Returns:
+            The code that should appear just before user-defined hooks.
+        """
+
     @abstractmethod
     def get_hooks(self) -> set[str]:
         """Get the React hooks for this component.
@@ -1141,14 +1149,28 @@ class Component(BaseComponent, ABC):
         """
         return
 
+    def get_hooks_internal(self) -> set[str]:
+        """Get the reflex internal hooks for the component and its children.
+
+        Returns:
+            The code that should appear just before user-defined hooks.
+        """
+        # Store the code in a set to avoid duplicates.
+        code = self._get_hooks_internal()
+
+        # Add the hook code for the children.
+        for child in self.children:
+            code |= child.get_hooks_internal()
+
+        return code
+
     def get_hooks(self) -> Set[str]:
         """Get the React hooks for this component and its children.
 
         Returns:
             The code that should appear just before returning the rendered component.
         """
-        # Store the code in a set to avoid duplicates.
-        code = self._get_hooks_internal()
+        code = set()
 
         # Add the hook code for this component.
         hooks = self._get_hooks()
@@ -1785,6 +1807,14 @@ class StatefulComponent(BaseComponent):
             )
         return trigger_memo
 
+    def get_hooks_internal(self) -> set[str]:
+        """Get the reflex internal hooks for the component and its children.
+
+        Returns:
+            The code that should appear just before user-defined hooks.
+        """
+        return set()
+
     def get_hooks(self) -> set[str]:
         """Get the React hooks for this component.
 
@@ -1893,7 +1923,7 @@ class MemoizationLeaf(Component):
             The memoization leaf
         """
         comp = super().create(*children, **props)
-        if comp.get_hooks():
+        if comp.get_hooks() or comp.get_hooks_internal():
             comp._memoization_mode = cls._memoization_mode.copy(
                 update={"disposition": MemoizationDisposition.ALWAYS}
             )

+ 1 - 1
reflex/components/el/elements/forms.py

@@ -162,7 +162,7 @@ class Form(BaseHTML):
         props["handle_submit_unique_name"] = ""
         form = super().create(*children, **props)
         form.handle_submit_unique_name = md5(
-            str(form.get_hooks()).encode("utf-8")
+            str(form.get_hooks_internal().union(form.get_hooks())).encode("utf-8")
         ).hexdigest()
         return form
 

+ 4 - 2
reflex/components/markdown/markdown.py

@@ -291,8 +291,10 @@ class Markdown(Component):
 
     def _get_custom_code(self) -> str | None:
         hooks = set()
-        for component in self.component_map.values():
-            hooks |= component(_MOCK_ARG).get_hooks()
+        for _component in self.component_map.values():
+            comp = _component(_MOCK_ARG)
+            hooks |= comp.get_hooks_internal()
+            hooks |= comp.get_hooks()
         formatted_hooks = "\n".join(hooks)
         return f"""
         function {self._get_component_map_name()} () {{