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

This commit is contained in:
Masen Furer 2023-12-01 09:49:59 -08:00 committed by GitHub
parent 3e9e718b89
commit 4ada79c1e5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 145 additions and 35 deletions

View File

@ -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 has_var_data:
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 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:

View File

@ -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.
"""
...

View File

@ -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"

View File

@ -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.
"""
...

View File

@ -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"

View File

@ -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,83 @@ class Recharts(Component):
"""
...
class RechartsCharts(NoSSRComponent):
class RechartsMemoizationLeafMixin(Component):
@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
) -> "RechartsMemoizationLeafMixin":
"""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.
"""
...
class RechartsCharts(NoSSRComponent, RechartsMemoizationLeafMixin):
@overload
@classmethod
def create( # type: ignore
@ -148,10 +225,10 @@ class RechartsCharts(NoSSRComponent):
] = None,
**props
) -> "RechartsCharts":
"""Create the component.
"""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 +238,7 @@ class RechartsCharts(NoSSRComponent):
**props: The props of the component.
Returns:
The component.
Raises:
TypeError: If an invalid child is passed.
A Recharts component.
"""
...

View File

@ -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):