Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix 663 #667

Merged
merged 5 commits into from
Feb 16, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions requirements/check-style.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
6 changes: 6 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -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
4 changes: 4 additions & 0 deletions src/idom/core/component.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
14 changes: 7 additions & 7 deletions src/idom/core/hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__",
Expand All @@ -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]]] = {
Expand All @@ -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:
Expand All @@ -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:
Expand All @@ -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()]

Expand Down
13 changes: 13 additions & 0 deletions src/idom/core/layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
7 changes: 7 additions & 0 deletions src/idom/core/proto.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,13 @@ class ComponentType(Protocol):
key: Key | None
"""An identifier which is unique amongst a component's immediate siblings"""

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

def render(self) -> VdomDict:
"""Render the component's :class:`VdomDict`."""

Expand Down
48 changes: 47 additions & 1 deletion tests/test_core/test_layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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