Repository navigation
Conversation
|
Merging this PR will regress 1 benchmark
|
| Benchmark | BASE |
HEAD |
Efficiency | |
|---|---|---|---|---|
| ❌ | test_var_access[mutable_dataclass_list] |
138 ms | 144.3 ms | -4.39% |
| ⚡ | test_hydration_metadata[20] |
220.5 µs | 207.8 µs | +6.14% |
| ⚡ | test_get_state[disk] |
955.8 µs | 918.3 µs | +4.08% |
Tip
Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.
Comparing benedikt-bartscher:client-state-var-seed-from-shared-value (3a0ebb4) with main (deea1c3)
Footnotes
-
18 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports. ↩
There was a problem hiding this comment.
All reported issues were addressed across 4 files (changes from recent commits).
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
FarhanAliRaza
left a comment
There was a problem hiding this comment.
I tested this PR in a small app that uses rx._x.client_state the way a downstream user does. I ran the same app on the PR and on its main merge base.
On main, each fault in the description occurs. A late reader behind rx.cond stays at clicked after a reset to the default. A push before any reader mounts raises refs._client_state_set... is not a function, and the reader shows the default. The on_load push with a reader behind is_hydrated shows the same error.
On the PR, all of these are correct, with no console errors. I also tested readers that unmount and mount again, client navigation away and back, a pushed None, a var with no default, a list default, retrieve after an early push, and pushes while a reader is mounted. A production export also succeeds.
One small change request is inline.
Type of change
Two fixes for a global
rx._x.client_statewhose readers mount after the value changed, such as one behind anrx.condor page content that only renders once the page is hydrated.1. Late readers keep a stale value
The value lives in
refs['_client_state_<name>']and every reader renders that shared value, but each reader's ownuseStatestarted from the default. A reader that mounts after the value was set shows the shared value while its local state holds the default. Setting the value back to the default then calls that reader's setter with a value equal to its local state, React skips the update, and the reader keeps showing the stale value.We hit this with a loading flag:
push(True)fromon_load,push(False)once a background query settles. Our layout only renders the page content onceis_hydratedis true, so after a websocket reconnect the content re-mounts between the two pushes and the spinner never goes away.Fix: for global client state,
useStatestarts from the shared value,useState('_client_state_<name>' in refs ? refs['_client_state_<name>'] : (<default>)), or justrefs['_client_state_<name>']without a default. Only a missing slot falls back to the default, so a set or pushednullis kept, and the parentheses keep defaults likea || bor ternaries intact. Local client state is unchanged.2. A push before any reader mounts fails
pushcompiled torefs['_client_state_set<name>'](value), and only a mounted component defines that setter. A backend push that arrives before one mounts, e.g. theon_loadpush above on a fresh load while the page waits for hydration, raisedTypeError: refs._client_state_set... is not a function.applyEventcatches it and reports it throughwindow.onerror(so it shows up in error tracking), and the value was lost, so the reader mounted with the default.Fix: without a setter, the push stores the value in the shared slot,
(refs['_client_state_set<name>'] ?? ((pushed) => { refs['_client_state_<name>'] = pushed; }))(value). With fix 1 the first reader to mount starts from it. This relies on fix 1: without it, the late reader would show the pushed value but keep the default as its own state.#6936 replaces this implementation with
useSyncExternalStore, which has neither problem, but keeps the current one available asrx._x.client_state.Tests
test_late_reader_follows_set_back_to_default: set the value, reveal a reader behindrx.cond, set the default again. Before fix 1 the reader stays atclicked.test_push_before_reader_mounts: push from the backend on a page where nothing uses the value yet, then reveal a reader. Before fix 2 the push raises and the reader showsinitial.test_push_none_before_reader_mounts: the same withpush(None), which has to survive instead of falling back to the default.useStatehook (global with and without a default, local) and the generated push script.test_client_state_setter_only_sibling_memo_initializes_state.