From 4ee3583b437fa439ce121aadc8a83bfe3ba2d98c Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Wed, 2 Jul 2025 20:16:00 -0700 Subject: [PATCH 1/5] use deterministic hash for stateful component names --- reflex/components/component.py | 47 +++++++++++++++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/reflex/components/component.py b/reflex/components/component.py index 8af5d832788..cc3ea5ffcd5 100644 --- a/reflex/components/component.py +++ b/reflex/components/component.py @@ -5,6 +5,7 @@ import contextlib import copy import dataclasses +import enum import functools import inspect import typing @@ -479,6 +480,49 @@ def _components_from( return () +def _deterministic_hash(value: object) -> int: + """Hash a rendered dictionary. + + Args: + value: The dictionary to hash. + + Returns: + The hash of the dictionary. + """ + if isinstance(value, BaseComponent): + # If the value is a component, hash its rendered code. + rendered_code = value.render() + return _deterministic_hash(rendered_code) + if isinstance(value, Var): + return _deterministic_hash((value._js_expr, value._get_all_var_data())) + if isinstance(value, VarData): + return _deterministic_hash(dataclasses.asdict(value)) + if isinstance(value, dict): + # Sort the dictionary to ensure consistent hashing. + return _deterministic_hash( + tuple(sorted((k, _deterministic_hash(v)) for k, v in value.items())) + ) + if isinstance(value, int): + # Hash numbers and booleans directly. + return int(value) + if isinstance(value, float): + return _deterministic_hash(str(value)) + if isinstance(value, str): + return int(md5(value.encode("utf-8")).hexdigest(), 16) + if isinstance(value, (tuple, list)): + # Hash tuples by hashing each element. + return _deterministic_hash("[" + ",".join(map(str, value)) + "]") + if isinstance(value, enum.Enum): + # Hash enums by their name. + return _deterministic_hash(value.name) + + msg = ( + f"Cannot hash value `{value}` of type `{type(value).__name__}`. " + "Only BaseComponent, Var, VarData, dict, str, tuple, and enum.Enum are supported." + ) + raise TypeError(msg) + + DEFAULT_TRIGGERS: Mapping[str, types.ArgsSpec | Sequence[types.ArgsSpec]] = { EventTriggers.ON_FOCUS: no_args_event_spec, EventTriggers.ON_BLUR: no_args_event_spec, @@ -2359,6 +2403,7 @@ def create(cls, component: Component) -> StatefulComponent | None: 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) + print(tag_name) if tag_name is None: return None @@ -2430,7 +2475,7 @@ def _get_tag_name(cls, component: Component) -> str | None: return None # Compute the hash based on the rendered code. - code_hash = md5(str(rendered_code).encode("utf-8")).hexdigest() + code_hash = _deterministic_hash(rendered_code) # Format the tag name including the hash. return format.format_state_name( From 5b150b4fc07e8c335cdd7144e6d9cf2007bcf803 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Wed, 2 Jul 2025 20:17:25 -0700 Subject: [PATCH 2/5] heh --- reflex/components/component.py | 1 - 1 file changed, 1 deletion(-) diff --git a/reflex/components/component.py b/reflex/components/component.py index cc3ea5ffcd5..63d66a3a1b1 100644 --- a/reflex/components/component.py +++ b/reflex/components/component.py @@ -2403,7 +2403,6 @@ def create(cls, component: Component) -> StatefulComponent | None: 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) - print(tag_name) if tag_name is None: return None From 303ecb28adc2732c3537cad198e15fb6de8f94a0 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Wed, 2 Jul 2025 20:25:10 -0700 Subject: [PATCH 3/5] dang it darglint --- reflex/components/component.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/reflex/components/component.py b/reflex/components/component.py index 63d66a3a1b1..9e6a9e5d3d2 100644 --- a/reflex/components/component.py +++ b/reflex/components/component.py @@ -488,6 +488,9 @@ def _deterministic_hash(value: object) -> int: Returns: The hash of the dictionary. + + Raises: + TypeError: If the value is not hashable. """ if isinstance(value, BaseComponent): # If the value is a component, hash its rendered code. From 4feed9c6d1c24a6faf672d8cdaa1d3fad1dad77e Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Thu, 3 Jul 2025 00:25:47 -0700 Subject: [PATCH 4/5] handle lists and None --- reflex/components/component.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/reflex/components/component.py b/reflex/components/component.py index 9e6a9e5d3d2..ab52346d91f 100644 --- a/reflex/components/component.py +++ b/reflex/components/component.py @@ -511,13 +511,18 @@ def _deterministic_hash(value: object) -> int: if isinstance(value, float): return _deterministic_hash(str(value)) if isinstance(value, str): - return int(md5(value.encode("utf-8")).hexdigest(), 16) + return int(md5(f'"{value}"'.encode()).hexdigest(), 16) if isinstance(value, (tuple, list)): # Hash tuples by hashing each element. - return _deterministic_hash("[" + ",".join(map(str, value)) + "]") + return _deterministic_hash( + "[" + ",".join(map(str, map(_deterministic_hash, value))) + "]" + ) if isinstance(value, enum.Enum): # Hash enums by their name. return _deterministic_hash(value.name) + if value is None: + # Hash None as a special case. + return _deterministic_hash("None") msg = ( f"Cannot hash value `{value}` of type `{type(value).__name__}`. " From fda9fad4318398a6643793178f97dfca32178345 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Thu, 3 Jul 2025 10:20:48 -0700 Subject: [PATCH 5/5] hash enum with str --- reflex/components/component.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reflex/components/component.py b/reflex/components/component.py index ab52346d91f..438d9404367 100644 --- a/reflex/components/component.py +++ b/reflex/components/component.py @@ -519,7 +519,7 @@ def _deterministic_hash(value: object) -> int: ) if isinstance(value, enum.Enum): # Hash enums by their name. - return _deterministic_hash(value.name) + return _deterministic_hash(str(value)) if value is None: # Hash None as a special case. return _deterministic_hash("None")