1
0
Эх сурвалжийг харах

[REF-1349] RechartsCharts and ResponsiveContainer must be memo leaf (#2240)

Masen Furer 1 жил өмнө
parent
commit
4ada79c1e5

+ 20 - 10
reflex/components/component.py

@@ -28,6 +28,7 @@ from reflex.constants import (
     EventTriggers,
     Hooks,
     Imports,
+    MemoizationDisposition,
     MemoizationMode,
     PageNames,
 )
@@ -1363,20 +1364,29 @@ class StatefulComponent(BaseComponent):
         """
         from reflex.components.layout.foreach import Foreach
 
+        if component._memoization_mode.disposition == MemoizationDisposition.NEVER:
+            # Never memoize this component.
+            return None
+
         if component.tag is None:
             # Only memoize components with a tag.
             return None
 
         # If _var_data is found in this component, it is a candidate for auto-memoization.
-        has_var_data = False
+        should_memoize = False
 
-        # Determine if any Vars have associated data.
-        for prop_var in component._get_vars():
-            if prop_var._var_data:
-                has_var_data = True
-                break
+        # If the component requests to be memoized, then ignore other checks.
+        if component._memoization_mode.disposition == MemoizationDisposition.ALWAYS:
+            should_memoize = True
+
+        if not should_memoize:
+            # Determine if any Vars have associated data.
+            for prop_var in component._get_vars():
+                if prop_var._var_data:
+                    should_memoize = True
+                    break
 
-        if not has_var_data:
+        if not should_memoize:
             # Check for special-cases in child components.
             for child in component.children:
                 # Skip BaseComponent and StatefulComponent children.
@@ -1384,14 +1394,14 @@ class StatefulComponent(BaseComponent):
                     continue
                 # Always consider Foreach something that must be memoized by the parent.
                 if isinstance(child, Foreach):
-                    has_var_data = True
+                    should_memoize = True
                     break
                 child = cls._child_var(child)
                 if isinstance(child, Var) and child._var_data:
-                    has_var_data = True
+                    should_memoize = True
                     break
 
-        if has_var_data or component.event_triggers:
+        if should_memoize or component.event_triggers:
             # Render the component to determine tag+hash based on component code.
             tag_name = cls._get_tag_name(component)
             if tag_name is None:

+ 3 - 6
reflex/components/graphing/recharts/charts.pyi

@@ -770,10 +770,10 @@ class FunnelChart(RechartsCharts):
         ] = None,
         **props
     ) -> "FunnelChart":
-        """Create the component.
+        """Create a Recharts chart container component (mixin).
 
         Args:
-            *children: The children of the component.
+            *children: The children components.
             data: The source data, in which each element is an object.
             sync_id: If any two categorical charts(rx.line_chart, rx.area_chart, rx.bar_chart, rx.composed_chart) have the same sync_id, these two charts can sync the position GraphingTooltip, and the start_index, end_index of Brush.
             sync_method: When sync_id is provided, allows customisation of how the charts will synchronize GraphingTooltips and brushes. Using 'index' (default setting), other charts will reuse current datum's index within the data array. In cases where data does not have the same length, this might yield unexpected results. In that case use 'value' which will try to match other charts values, or a fully custom function which will receive tick, data as argument and should return an index. 'index' | 'value' | function
@@ -791,10 +791,7 @@ class FunnelChart(RechartsCharts):
             **props: The props of the component.
 
         Returns:
-            The component.
-
-        Raises:
-            TypeError: If an invalid child is passed.
+            A Recharts component.
         """
         ...
 

+ 2 - 1
reflex/components/graphing/recharts/general.py

@@ -13,10 +13,11 @@ from .recharts import (
     LiteralPosition,
     LiteralVerticalAlign,
     Recharts,
+    RechartsMemoizationLeafMixin,
 )
 
 
-class ResponsiveContainer(Recharts):
+class ResponsiveContainer(Recharts, RechartsMemoizationLeafMixin):
     """A base class for responsive containers in Recharts."""
 
     tag = "ResponsiveContainer"

+ 5 - 7
reflex/components/graphing/recharts/general.pyi

@@ -17,9 +17,10 @@ from .recharts import (
     LiteralPosition,
     LiteralVerticalAlign,
     Recharts,
+    RechartsMemoizationLeafMixin,
 )
 
-class ResponsiveContainer(Recharts):
+class ResponsiveContainer(Recharts, RechartsMemoizationLeafMixin):
     @overload
     @classmethod
     def create(  # type: ignore
@@ -84,10 +85,10 @@ class ResponsiveContainer(Recharts):
         ] = None,
         **props
     ) -> "ResponsiveContainer":
-        """Create the component.
+        """Create a Recharts chart container component (mixin).
 
         Args:
-            *children: The children of the component.
+            *children: The children components.
             aspect: The aspect ratio of the container. The final aspect ratio of the SVG element will be (width / height) * aspect. Number
             width: The width of chart container. Can be a number or string
             height: The height of chart container. Number
@@ -103,10 +104,7 @@ class ResponsiveContainer(Recharts):
             **props: The props of the component.
 
         Returns:
-            The component.
-
-        Raises:
-            TypeError: If an invalid child is passed.
+            A Recharts component.
         """
         ...
 

+ 32 - 3
reflex/components/graphing/recharts/recharts.py

@@ -2,16 +2,45 @@
 from typing import Literal
 
 from reflex.components.component import Component, NoSSRComponent
+from reflex.constants import MemoizationDisposition, MemoizationMode
 
 
 class Recharts(Component):
-    """A component that wraps a victory lib."""
+    """A component that wraps a recharts lib."""
 
     library = "recharts@2.8.0"
 
 
-class RechartsCharts(NoSSRComponent):
-    """A component that wraps a victory lib."""
+class RechartsMemoizationLeafMixin(Component):
+    """A mixin for Recharts components that must not memoize their children separately.
+
+    This includes all chart types and ResponsiveContainer itself.
+    """
+
+    _memoization_mode = MemoizationMode(recursive=False)
+
+    @classmethod
+    def create(cls, *children, **props) -> Component:
+        """Create a Recharts chart container component (mixin).
+
+        Args:
+            *children: The children components.
+            **props: The props of the component.
+
+        Returns:
+            A Recharts component.
+        """
+        comp = super().create(*children, **props)
+        if comp.get_hooks():
+            # If any of the children depend on state, then this instance needs to memoize.
+            comp._memoization_mode = cls._memoization_mode.copy(
+                update={"disposition": MemoizationDisposition.ALWAYS},
+            )
+        return comp
+
+
+class RechartsCharts(NoSSRComponent, RechartsMemoizationLeafMixin):
+    """A component that wraps a recharts lib."""
 
     library = "recharts@2.8.0"
 

+ 81 - 7
reflex/components/graphing/recharts/recharts.pyi

@@ -9,6 +9,7 @@ from reflex.event import EventChain, EventHandler, EventSpec
 from reflex.style import Style
 from typing import Literal
 from reflex.components.component import Component, NoSSRComponent
+from reflex.constants import MemoizationDisposition, MemoizationMode
 
 class Recharts(Component):
     @overload
@@ -89,7 +90,7 @@ class Recharts(Component):
         """
         ...
 
-class RechartsCharts(NoSSRComponent):
+class RechartsMemoizationLeafMixin(Component):
     @overload
     @classmethod
     def create(  # type: ignore
@@ -147,11 +148,11 @@ class RechartsCharts(NoSSRComponent):
             Union[EventHandler, EventSpec, list, function, BaseVar]
         ] = None,
         **props
-    ) -> "RechartsCharts":
-        """Create the component.
+    ) -> "RechartsMemoizationLeafMixin":
+        """Create a Recharts chart container component (mixin).
 
         Args:
-            *children: The children of the component.
+            *children: The children components.
             style: The style of the component.
             key: A unique key for the component.
             id: The id for the component.
@@ -161,10 +162,83 @@ class RechartsCharts(NoSSRComponent):
             **props: The props of the component.
 
         Returns:
-            The component.
+            A Recharts component.
+        """
+        ...
 
-        Raises:
-            TypeError: If an invalid child is passed.
+class RechartsCharts(NoSSRComponent, RechartsMemoizationLeafMixin):
+    @overload
+    @classmethod
+    def create(  # type: ignore
+        cls,
+        *children,
+        style: Optional[Style] = None,
+        key: Optional[Any] = None,
+        id: Optional[Any] = None,
+        class_name: Optional[Any] = None,
+        autofocus: Optional[bool] = None,
+        custom_attrs: Optional[Dict[str, Union[Var, str]]] = None,
+        on_blur: Optional[
+            Union[EventHandler, EventSpec, list, function, BaseVar]
+        ] = None,
+        on_click: Optional[
+            Union[EventHandler, EventSpec, list, function, BaseVar]
+        ] = None,
+        on_context_menu: Optional[
+            Union[EventHandler, EventSpec, list, function, BaseVar]
+        ] = None,
+        on_double_click: Optional[
+            Union[EventHandler, EventSpec, list, function, BaseVar]
+        ] = None,
+        on_focus: Optional[
+            Union[EventHandler, EventSpec, list, function, BaseVar]
+        ] = None,
+        on_mount: Optional[
+            Union[EventHandler, EventSpec, list, function, BaseVar]
+        ] = None,
+        on_mouse_down: Optional[
+            Union[EventHandler, EventSpec, list, function, BaseVar]
+        ] = None,
+        on_mouse_enter: Optional[
+            Union[EventHandler, EventSpec, list, function, BaseVar]
+        ] = None,
+        on_mouse_leave: Optional[
+            Union[EventHandler, EventSpec, list, function, BaseVar]
+        ] = None,
+        on_mouse_move: Optional[
+            Union[EventHandler, EventSpec, list, function, BaseVar]
+        ] = None,
+        on_mouse_out: Optional[
+            Union[EventHandler, EventSpec, list, function, BaseVar]
+        ] = None,
+        on_mouse_over: Optional[
+            Union[EventHandler, EventSpec, list, function, BaseVar]
+        ] = None,
+        on_mouse_up: Optional[
+            Union[EventHandler, EventSpec, list, function, BaseVar]
+        ] = None,
+        on_scroll: Optional[
+            Union[EventHandler, EventSpec, list, function, BaseVar]
+        ] = None,
+        on_unmount: Optional[
+            Union[EventHandler, EventSpec, list, function, BaseVar]
+        ] = None,
+        **props
+    ) -> "RechartsCharts":
+        """Create a Recharts chart container component (mixin).
+
+        Args:
+            *children: The children components.
+            style: The style of the component.
+            key: A unique key for the component.
+            id: The id for the component.
+            class_name: The class name for the component.
+            autofocus: Whether the component should take the focus once the page is loaded
+            custom_attrs: custom attribute
+            **props: The props of the component.
+
+        Returns:
+            A Recharts component.
         """
         ...
 

+ 2 - 1
reflex/constants/compiler.py

@@ -115,7 +115,8 @@ class MemoizationDisposition(enum.Enum):
 
     # If the component uses state or events, it should be memoized.
     STATEFUL = "stateful"
-    # TODO: add more modes, like always and never
+    ALWAYS = "always"
+    NEVER = "never"
 
 
 class MemoizationMode(Base):