From 353fe09a99e65089da6cd38f1642d5558beebb3d Mon Sep 17 00:00:00 2001 From: mathieu Date: Sat, 16 May 2026 21:49:33 +0200 Subject: [PATCH] fix(hosts,applications): fix wasm-bindgen panic when closing modal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closing a modal by clicking the backdrop, Cancel, or × called show_modal.set(false) synchronously inside a wasm-bindgen closure. Leptos immediately unmounts the modal, freeing all its closures while the click handler is still on the call stack, which causes wasm-bindgen to panic with "closure invoked after being dropped". Fix: introduce a close() helper that defers set(false) to the next microtask via spawn_local, so the closure returns to wasm-bindgen before the modal is unmounted. Also switch autofocus from Effect+get() to spawn_local+get_untracked() to avoid subscribing NodeRef as a reactive dependency, which would re-trigger during unmount and risk the same panic. Co-Authored-By: Claude Sonnet 4.6 --- src/client/applications.rs | 23 +++++++++++++++++------ src/client/hosts.rs | 15 +++++++++------ 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/src/client/applications.rs b/src/client/applications.rs index 8fc67d8..906bb9c 100644 --- a/src/client/applications.rs +++ b/src/client/applications.rs @@ -22,22 +22,33 @@ fn AddApplicationModal( create_action: ServerAction, show_modal: RwSignal, ) -> impl IntoView { + use leptos::task::spawn_local; + let name_ref = NodeRef::::new(); - // Focus the name field as soon as the modal is mounted. - Effect::new(move |_| { - if let Some(el) = name_ref.get() { + // Defer focus to the next microtask so the element is in the DOM. + // Using get_untracked() avoids subscribing to NodeRef's reactive signal, + // which would otherwise re-trigger during modal unmount and cause + // "closure invoked after being dropped" in wasm-bindgen. + spawn_local(async move { + if let Some(el) = name_ref.get_untracked() { let _ = el.focus(); } }); + // close() defers show_modal.set(false) to the next microtask. + // Without this, setting the signal synchronously inside a click handler + // unmounts the modal (and frees its closures) while the handler is still + // on the call stack, causing wasm-bindgen to panic. + let close = move || spawn_local(async move { show_modal.set(false) }); + view! { -