2
2
3
3
import logging
4
4
from asyncio import Event , Task , create_task , gather
5
+ from contextvars import ContextVar , Token
5
6
from typing import Any , Callable , Protocol , TypeVar
6
7
7
8
from anyio import Semaphore
8
9
9
- from reactpy .core ._thread_local import ThreadLocal
10
10
from reactpy .core .types import ComponentType , Context , ContextProviderType
11
11
12
12
T = TypeVar ("T" )
@@ -18,12 +18,27 @@ async def __call__(self, stop: Event) -> None: ...
18
18
19
19
logger = logging .getLogger (__name__ )
20
20
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 ()
22
37
23
38
24
39
def current_hook () -> LifeCycleHook :
25
40
"""Get the current :class:`LifeCycleHook`"""
26
- hook_stack = _HOOK_STATE .get ()
41
+ hook_stack = _hook_state .get ()
27
42
if not hook_stack :
28
43
msg = "No life cycle hook is active. Are you rendering in a layout?"
29
44
raise RuntimeError (msg )
@@ -130,7 +145,7 @@ def __init__(
130
145
self ._scheduled_render = False
131
146
self ._rendered_atleast_once = False
132
147
self ._current_state_index = 0
133
- self ._state : tuple [ Any , ...] = ()
148
+ self ._state : list = []
134
149
self ._effect_funcs : list [EffectFunc ] = []
135
150
self ._effect_tasks : list [Task [None ]] = []
136
151
self ._effect_stops : list [Event ] = []
@@ -157,7 +172,7 @@ def use_state(self, function: Callable[[], T]) -> T:
157
172
if not self ._rendered_atleast_once :
158
173
# since we're not initialized yet we're just appending state
159
174
result = function ()
160
- self ._state += (result , )
175
+ self ._state . append (result )
161
176
else :
162
177
# once finalized we iterate over each succesively used piece of state
163
178
result = self ._state [self ._current_state_index ]
@@ -232,13 +247,13 @@ def set_current(self) -> None:
232
247
This method is called by a layout before entering the render method
233
248
of this hook's associated component.
234
249
"""
235
- hook_stack = _HOOK_STATE . get ()
250
+ hook_stack = get_hook_state ()
236
251
if hook_stack :
237
252
parent = hook_stack [- 1 ]
238
253
self ._context_providers .update (parent ._context_providers )
239
254
hook_stack .append (self )
240
255
241
256
def unset_current (self ) -> None :
242
257
"""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 :
244
259
raise RuntimeError ("Hook stack is in an invalid state" ) # nocov
0 commit comments