Skip to content

Commit

Permalink
[REF-1349] RechartsCharts and ResponsiveContainer must be memo leaf (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
masenf authored Dec 1, 2023
1 parent 3e9e718 commit 4ada79c
Show file tree
Hide file tree
Showing 7 changed files with 145 additions and 35 deletions.
30 changes: 20 additions & 10 deletions reflex/components/component.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
EventTriggers,
Hooks,
Imports,
MemoizationDisposition,
MemoizationMode,
PageNames,
)
Expand Down Expand Up @@ -1363,35 +1364,44 @@ def create(cls, component: Component) -> StatefulComponent | None:
"""
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.
if not isinstance(child, Component):
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:
Expand Down
9 changes: 3 additions & 6 deletions reflex/components/graphing/recharts/charts.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.
"""
...

Expand Down
3 changes: 2 additions & 1 deletion reflex/components/graphing/recharts/general.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@
LiteralPosition,
LiteralVerticalAlign,
Recharts,
RechartsMemoizationLeafMixin,
)


class ResponsiveContainer(Recharts):
class ResponsiveContainer(Recharts, RechartsMemoizationLeafMixin):
"""A base class for responsive containers in Recharts."""

tag = "ResponsiveContainer"
Expand Down
12 changes: 5 additions & 7 deletions reflex/components/graphing/recharts/general.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@ from .recharts import (
LiteralPosition,
LiteralVerticalAlign,
Recharts,
RechartsMemoizationLeafMixin,
)

class ResponsiveContainer(Recharts):
class ResponsiveContainer(Recharts, RechartsMemoizationLeafMixin):
@overload
@classmethod
def create( # type: ignore
Expand Down Expand Up @@ -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
Expand All @@ -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.
"""
...

Expand Down
35 changes: 32 additions & 3 deletions reflex/components/graphing/recharts/recharts.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down
88 changes: 81 additions & 7 deletions reflex/components/graphing/recharts/recharts.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -89,7 +90,7 @@ class Recharts(Component):
"""
...

class RechartsCharts(NoSSRComponent):
class RechartsMemoizationLeafMixin(Component):
@overload
@classmethod
def create( # type: ignore
Expand Down Expand Up @@ -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.
Expand All @@ -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.
"""
...

Expand Down
3 changes: 2 additions & 1 deletion reflex/constants/compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down

0 comments on commit 4ada79c

Please sign in to comment.