1- from uuid import uuid4
1+ from uuid import UUID , uuid4
22
33from reactpy import component , html , use_location , use_state
44from reactpy_router import link , route , use_params , use_search_params
77from reactpy_django .router import django_router
88
99
10+ class _TokenStore :
11+ """Process-wide holder for a stable navigation-state token."""
12+
13+ def __init__ (self ) -> None :
14+ self ._value : UUID | None = None
15+
16+ def get (self ) -> UUID :
17+ """Return the token, creating one on first access."""
18+ if self ._value is None :
19+ self ._value = uuid4 ()
20+ return self ._value
21+
22+
23+ _NEXT_PAGE_TOKEN = _TokenStore ()
24+
25+
1026@component
1127def display_params (string : str ):
1228 location = use_location ()
@@ -32,7 +48,13 @@ def show_route(path: str, *children: Route) -> Route:
3248@component
3349def next_page ():
3450 url_params = use_params ()
35- state , _set_state = use_state (uuid4 )
51+ # ReactPy preserves `use_state` across SPA navigation, but the WebSocket can
52+ # reconnect under load, which causes ReactPy to re-mount the component and
53+ # reset hook state. Keep the token in a module-level store so it survives both
54+ # navigation and transient reconnects, otherwise this state-preservation test
55+ # becomes flaky on slow/loaded CI runners.
56+ token_hex = _NEXT_PAGE_TOKEN .get ()
57+ state , _set_state = use_state (token_hex )
3658 page = url_params .get ("page" , 0 )
3759 next_page = page + 1
3860 return html .fragment (
@@ -44,17 +66,24 @@ def next_page():
4466 )
4567
4668
69+ # Routes are defined at module scope so that the route elements (including the
70+ # stateful `next_page`) have a stable identity across renders. Recreating them
71+ # inside `main()` on each render would cause ReactPy to treat them as new
72+ # components, remounting them and resetting `use_state` during SPA navigation.
73+ ROUTES : tuple [Route , ...] = (
74+ show_route ("/router/" , show_route ("subroute/" )),
75+ show_route ("/router/unspecified/<value>/" ),
76+ show_route ("/router/integer/<int:value>/" ),
77+ show_route ("/router/path/<path:value>/" ),
78+ show_route ("/router/slug/<slug:value>/" ),
79+ show_route ("/router/string/<str:value>/" ),
80+ show_route ("/router/uuid/<uuid:value>/" ),
81+ show_route ("/router/any/<any:name>" ),
82+ show_route ("/router/two/<int:value>/<str:value2>/" ),
83+ route ("/router/next/<int:page>/" , next_page ()),
84+ )
85+
86+
4787@component
4888def main ():
49- return django_router (
50- show_route ("/router/" , show_route ("subroute/" )),
51- show_route ("/router/unspecified/<value>/" ),
52- show_route ("/router/integer/<int:value>/" ),
53- show_route ("/router/path/<path:value>/" ),
54- show_route ("/router/slug/<slug:value>/" ),
55- show_route ("/router/string/<str:value>/" ),
56- show_route ("/router/uuid/<uuid:value>/" ),
57- show_route ("/router/any/<any:name>" ),
58- show_route ("/router/two/<int:value>/<str:value2>/" ),
59- route ("/router/next/<int:page>/" , next_page ()),
60- )
89+ return django_router (* ROUTES )
0 commit comments