22
33import logging
44from asyncio import Event , Task , create_task , gather
5+ from contextvars import ContextVar , Token
56from typing import Any , Callable , Protocol , TypeVar
67
78from anyio import Semaphore
89
9- from reactpy .core ._thread_local import ThreadLocal
1010from reactpy .core .types import ComponentType , Context , ContextProviderType
1111
1212T = TypeVar ("T" )
@@ -18,12 +18,27 @@ async def __call__(self, stop: Event) -> None: ...
1818
1919logger = logging .getLogger (__name__ )
2020
21- _HOOK_STATE : ThreadLocal [list [LifeCycleHook ]] = ThreadLocal (list )
21+ _hook_state = ContextVar ("_hook_state" )
22+
23+
24+ def create_hook_state (initial : list | None = None ) -> Token [list ]:
25+ return _hook_state .set (initial or [])
26+
27+
28+ def clear_hook_state (token : Token [list ]) -> None :
29+ hook_stack = _hook_state .get ()
30+ if hook_stack :
31+ logger .warning ("clear_hook_state: Hook stack was not empty" )
32+ _hook_state .reset (token )
33+
34+
35+ def get_hook_state () -> list [LifeCycleHook ]:
36+ return _hook_state .get ()
2237
2338
2439def current_hook () -> LifeCycleHook :
2540 """Get the current :class:`LifeCycleHook`"""
26- hook_stack = _HOOK_STATE .get ()
41+ hook_stack = _hook_state .get ()
2742 if not hook_stack :
2843 msg = "No life cycle hook is active. Are you rendering in a layout?"
2944 raise RuntimeError (msg )
@@ -130,7 +145,7 @@ def __init__(
130145 self ._scheduled_render = False
131146 self ._rendered_atleast_once = False
132147 self ._current_state_index = 0
133- self ._state : tuple [ Any , ...] = ()
148+ self ._state : list = []
134149 self ._effect_funcs : list [EffectFunc ] = []
135150 self ._effect_tasks : list [Task [None ]] = []
136151 self ._effect_stops : list [Event ] = []
@@ -157,7 +172,7 @@ def use_state(self, function: Callable[[], T]) -> T:
157172 if not self ._rendered_atleast_once :
158173 # since we're not initialized yet we're just appending state
159174 result = function ()
160- self ._state += (result , )
175+ self ._state . append (result )
161176 else :
162177 # once finalized we iterate over each succesively used piece of state
163178 result = self ._state [self ._current_state_index ]
@@ -232,13 +247,13 @@ def set_current(self) -> None:
232247 This method is called by a layout before entering the render method
233248 of this hook's associated component.
234249 """
235- hook_stack = _HOOK_STATE . get ()
250+ hook_stack = get_hook_state ()
236251 if hook_stack :
237252 parent = hook_stack [- 1 ]
238253 self ._context_providers .update (parent ._context_providers )
239254 hook_stack .append (self )
240255
241256 def unset_current (self ) -> None :
242257 """Unset this hook as the active hook in this thread"""
243- if _HOOK_STATE . get ().pop () is not self :
258+ if get_hook_state ().pop () is not self :
244259 raise RuntimeError ("Hook stack is in an invalid state" ) # nocov
0 commit comments