From a3f33f45405b152dc2273bfc07d18d54a6812f4d Mon Sep 17 00:00:00 2001 From: rmorshea Date: Sat, 5 Feb 2022 14:22:21 -0800 Subject: [PATCH 1/5] configure pydocstyle --- requirements/check-style.txt | 2 ++ setup.cfg | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/requirements/check-style.txt b/requirements/check-style.txt index df22cfea8..4a6e0c421 100644 --- a/requirements/check-style.txt +++ b/requirements/check-style.txt @@ -5,3 +5,5 @@ flake8-print flake8-tidy-imports isort >=5.7.0 pep8-naming +# pydocstyle +git+https://github.com/PyCQA/pydocstyle.git@bd4993345a241bd0d5128f21de56394b1cde3714#egg=pydocstyle diff --git a/setup.cfg b/setup.cfg index 4359702ff..0e801da50 100644 --- a/setup.cfg +++ b/setup.cfg @@ -42,3 +42,9 @@ omit = all-files = true source-dir = docs/source build-dir = docs/build + +[pydocstyle] +inherit = false +match = .*\.py +convention = google +add_ignore = D100,D101,D102,D103,D104,D105,D107,D412,D415 From 1891eb49d3e3bc3be911fda676efc2f1f8058c93 Mon Sep 17 00:00:00 2001 From: rmorshea Date: Wed, 9 Feb 2022 09:02:41 -0800 Subject: [PATCH 2/5] compare definition ID to see if changed in layout --- src/idom/core/component.py | 4 ++++ src/idom/core/layout.py | 4 ++++ src/idom/core/proto.py | 6 ++++++ 3 files changed, 14 insertions(+) diff --git a/src/idom/core/component.py b/src/idom/core/component.py index 9c154592c..ccc76c070 100644 --- a/src/idom/core/component.py +++ b/src/idom/core/component.py @@ -51,6 +51,10 @@ def __init__( self._kwargs = kwargs self.key = key + @property + def definition_id(self) -> int: + return id(self._func) + def render(self) -> VdomDict: model = self._func(*self._args, **self._kwargs) if isinstance(model, ComponentType): diff --git a/src/idom/core/layout.py b/src/idom/core/layout.py index 870ede478..c0f944bb6 100644 --- a/src/idom/core/layout.py +++ b/src/idom/core/layout.py @@ -511,6 +511,10 @@ def _update_component_model_state( life_cycle_state=( _update_life_cycle_state(old_model_state.life_cycle_state, new_component) if old_model_state.is_component_state + and ( + old_model_state.life_cycle_state.component.definition_id + == new_component.definition_id + ) else _make_life_cycle_state(new_component, schedule_render) ), ) diff --git a/src/idom/core/proto.py b/src/idom/core/proto.py index e12120533..1859eefa7 100644 --- a/src/idom/core/proto.py +++ b/src/idom/core/proto.py @@ -32,6 +32,12 @@ class ComponentType(Protocol): key: Key | None """An identifier which is unique amongst a component's immediate siblings""" + definition_id: int + """A globally unique identifier for this component definition. + + Usually the :func:`id` of this class or an underlying function. + """ + def render(self) -> VdomDict: """Render the component's :class:`VdomDict`.""" From 2c3d5346157cd5c878e8ff2f314ff5474ef431bc Mon Sep 17 00:00:00 2001 From: rmorshea Date: Wed, 9 Feb 2022 09:03:11 -0800 Subject: [PATCH 3/5] fix mispellings --- src/idom/core/hooks.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/idom/core/hooks.py b/src/idom/core/hooks.py index 0aeb2a0d5..50bcd1da1 100644 --- a/src/idom/core/hooks.py +++ b/src/idom/core/hooks.py @@ -501,7 +501,7 @@ class LifeCycleHook: "_schedule_render_later", "_current_state_index", "_state", - "_rendered_atleast_once", + "_rendered_at_least_once", "_is_rendering", "_event_effects", "__weakref__", @@ -514,7 +514,7 @@ def __init__( self._schedule_render_callback = schedule_render self._schedule_render_later = False self._is_rendering = False - self._rendered_atleast_once = False + self._rendered_at_least_once = False self._current_state_index = 0 self._state: Tuple[Any, ...] = () self._event_effects: Dict[EffectType, List[Callable[[], None]]] = { @@ -530,18 +530,18 @@ def schedule_render(self) -> None: return None def use_state(self, function: Callable[[], _StateType]) -> _StateType: - if not self._rendered_atleast_once: + if not self._rendered_at_least_once: # since we're not intialized yet we're just appending state result = function() self._state += (result,) else: - # once finalized we iterate over each succesively used piece of state + # once finalized we iterate over each successively used piece of state result = self._state[self._current_state_index] self._current_state_index += 1 return result def add_effect(self, effect_type: EffectType, function: Callable[[], None]) -> None: - """Trigger a function on the occurance of the given effect type""" + """Trigger a function on the occurrence of the given effect type""" self._event_effects[effect_type].append(function) def component_will_render(self) -> None: @@ -562,7 +562,7 @@ def component_did_render(self) -> None: self._is_rendering = False if self._schedule_render_later: self._schedule_render() - self._rendered_atleast_once = True + self._rendered_at_least_once = True self._current_state_index = 0 def component_will_unmount(self) -> None: @@ -585,7 +585,7 @@ def set_current(self) -> None: def unset_current(self) -> None: """Unset this hook as the active hook in this thread""" - # this assertion should never fail - primarilly useful for debug + # this assertion should never fail - primarily useful for debug assert _current_life_cycle_hook[get_thread_id()] is self del _current_life_cycle_hook[get_thread_id()] From 048dd91f579282f8fb1aec0bfbffab615c74f7c3 Mon Sep 17 00:00:00 2001 From: rmorshea Date: Wed, 9 Feb 2022 22:36:19 -0800 Subject: [PATCH 4/5] try another approach to fixing switching component def and resetting state --- src/idom/core/layout.py | 17 +++++++++--- tests/test_core/test_layout.py | 48 +++++++++++++++++++++++++++++++++- 2 files changed, 60 insertions(+), 5 deletions(-) diff --git a/src/idom/core/layout.py b/src/idom/core/layout.py index c0f944bb6..cf7824808 100644 --- a/src/idom/core/layout.py +++ b/src/idom/core/layout.py @@ -380,6 +380,19 @@ def _render_model_children( child, self._rendering_queue.put, ) + elif old_child_state.is_component_state and ( + old_child_state.life_cycle_state.component.definition_id + != child.definition_id + ): + self._unmount_model_states([old_child_state]) + old_child_state = None + new_child_state = _make_component_model_state( + new_state, + index, + key, + child, + self._rendering_queue.put, + ) else: new_child_state = _update_component_model_state( old_child_state, @@ -511,10 +524,6 @@ def _update_component_model_state( life_cycle_state=( _update_life_cycle_state(old_model_state.life_cycle_state, new_component) if old_model_state.is_component_state - and ( - old_model_state.life_cycle_state.component.definition_id - == new_component.definition_id - ) else _make_life_cycle_state(new_component, schedule_render) ), ) diff --git a/tests/test_core/test_layout.py b/tests/test_core/test_layout.py index 46fb32112..879e7a7bd 100644 --- a/tests/test_core/test_layout.py +++ b/tests/test_core/test_layout.py @@ -11,7 +11,7 @@ from idom import html from idom.config import IDOM_DEBUG_MODE from idom.core.dispatcher import render_json_patch -from idom.core.hooks import use_effect +from idom.core.hooks import use_effect, use_state from idom.core.layout import LayoutEvent from idom.testing import ( HookCatcher, @@ -877,3 +877,49 @@ def SomeComponent(): assert element_static_handler.target in layout._event_handlers assert component_static_handler.target not in layout._event_handlers + + +async def test_switching_component_definition(): + toggle_component = idom.Ref() + first_used_state = idom.Ref(None) + second_used_state = idom.Ref(None) + + @idom.component + def Root(): + toggle, toggle_component.current = use_toggle(True) + if toggle: + return FirstComponent() + else: + return SecondComponent() + + @idom.component + def FirstComponent(): + first_used_state.current = use_state("first")[0] + # reset state after unmount + use_effect(lambda: lambda: first_used_state.set_current(None)) + return html.div() + + @idom.component + def SecondComponent(): + second_used_state.current = use_state("second")[0] + # reset state after unmount + use_effect(lambda: lambda: second_used_state.set_current(None)) + return html.div() + + with idom.Layout(Root()) as layout: + await layout.render() + + assert first_used_state.current == "first" + assert second_used_state.current is None + + toggle_component.current() + await layout.render() + + assert first_used_state.current is None + assert second_used_state.current == "second" + + toggle_component.current() + await layout.render() + + assert first_used_state.current == "first" + assert second_used_state.current is None From f01e7c86aec37a3941cf4e76d3d6984077ea37ea Mon Sep 17 00:00:00 2001 From: rmorshea Date: Fri, 11 Feb 2022 11:35:27 -0800 Subject: [PATCH 5/5] fix types --- src/idom/core/proto.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/idom/core/proto.py b/src/idom/core/proto.py index 1859eefa7..8a5782ca8 100644 --- a/src/idom/core/proto.py +++ b/src/idom/core/proto.py @@ -32,11 +32,12 @@ class ComponentType(Protocol): key: Key | None """An identifier which is unique amongst a component's immediate siblings""" - definition_id: int - """A globally unique identifier for this component definition. + @property + def definition_id(self) -> int: + """A globally unique identifier for this component definition. - Usually the :func:`id` of this class or an underlying function. - """ + Usually the :func:`id` of this class or an underlying function. + """ def render(self) -> VdomDict: """Render the component's :class:`VdomDict`."""