You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
useEffect + setState panics with RefCell already borrowed on macOS ARM64 #4
Been playing around with perry-react today and hit a crash that I think is also responsible for the weather-app demo crashing on launch.
Calling a state setter inside useEffect panics every time:
thread '<unnamed>' panicked at crates/perry-ui-macos/src/state.rs:382:13:
RefCell already borrowed
My guess is the useEffect fires and triggers a re-render while the initial render is still holding a borrow on the state RefCell.
I initially found this when cloning & testing the weather-app and it crashes on launch with the same abort — pretty sure it's the same root cause since it calls setState from inside a fetch callback on startup. I couldn't get weather-app working so started a new react repo and ultimately hit this error
macOS ARM64, built from source, perry-react from PerryTS/react.
Hey Thomas, thanks for the detailed report — your diagnosis was spot on.
Perry-react is admittedly the least tested part of our current UI surface, so really appreciate you putting it through its paces and taking the time to file this.
Root cause:state_set() held an immutable borrow on the ON_CHANGE_CALLBACKS RefCell while iterating and invoking user closures via js_closure_call1. In perry-react, useEffect fires synchronously during the initial render — so when setStatus("started") triggers state_set → onChange callback → _scheduleRerender → component re-render → any new useState calls sig.onChange() → state_on_change() tries borrow_mut() on the same RefCell → panic.
Fix:state_set() now snapshots the callback list into a local Vec before invoking any closures, releasing the RefCell borrow first. Same treatment applied to FOR_EACH_BINDINGS (which also calls user closures). Fixed across all 5 platform crates (macOS, iOS, GTK4, Windows, Android).
Activity
Fix RefCell state callback panic (GH-4) and fetch linker error (GH-5)
proggeramlug commentedon Mar 14, 2026
Hey Thomas, thanks for the detailed report — your diagnosis was spot on.
Perry-react is admittedly the least tested part of our current UI surface, so really appreciate you putting it through its paces and taking the time to file this.
Root cause:
state_set()held an immutable borrow on theON_CHANGE_CALLBACKSRefCell while iterating and invoking user closures viajs_closure_call1. In perry-react,useEffectfires synchronously during the initial render — so whensetStatus("started")triggersstate_set→onChangecallback →_scheduleRerender→ component re-render → any newuseStatecallssig.onChange()→state_on_change()triesborrow_mut()on the same RefCell → panic.Fix:
state_set()now snapshots the callback list into a localVecbefore invoking any closures, releasing the RefCell borrow first. Same treatment applied toFOR_EACH_BINDINGS(which also calls user closures). Fixed across all 5 platform crates (macOS, iOS, GTK4, Windows, Android).Fixed in
b521064.tomkennedy22 commentedon Mar 14, 2026
Awesome! Amazing response time, much appreciated. I'll test it out more on Saturday
feat(ui): #185 Phase C step 6 — hex strings + gradient (v0.5.310)
perf(transform): static-trip-count for-loop full-unroll + smart chann…
fix(compile): honor "*"/"@scope/*" wildcard in perry.compilePackages …
fix: class async / async-generator method test262 parity
fix: class async / async-generator method test262 parity (#4768)
fix(compile): warm auto-optimize cache dropped CPU-only well-known li…
fix(codegen): globalize module-init lets nested in destructure Try sc…
perf(codegen+runtime): representation-selection Phase 4a — fast plain…
fix(gc): run old-gen reclaim precisely at safepoints, stop scanning o…
fix(object): a Buffer/DataView receiver must not reach the ordinary o…
perf(runtime): route the shadow-stack TLS through the hot-cache fast …
perf(runtime): route the shadow-stack TLS through the hot-cache fast …
refactor(stdlib): remove nanoid native binding
refactor(stdlib): remove nanoid native binding
revert(runtime): drop the canonical slot-index carry (2b2cb6e, 3f73dd4)
revert(runtime): drop the canonical slot-index carry (2b2cb6e, 3f73dd4)
revert(runtime): drop the canonical slot-index carry (2b2cb6e, 3f73dd4)
revert(runtime): drop the canonical slot-index carry (2b2cb6e, 3f73dd4)
revert(runtime): drop the canonical slot-index carry (2b2cb6e, 3f73dd4)
gc-effects: nounwind from the linked table (slice 9); keys arrays res…
cargo config: Android std has real thread-locals (one emutls key, not…