diff --git a/src/main.rs b/src/main.rs index 60b4923..a82a425 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,6 +5,7 @@ use tokio::sync::Mutex; use compareware::db::Database; use compareware::api::{ItemRequest, create_item, get_items, get_selected_properties, add_selected_property}; use compareware::models::item::Item; +use compareware::utils::panic_hook; #[cfg(feature = "ssr")] #[actix_web::main] @@ -18,6 +19,8 @@ async fn main() -> std::io::Result<()> { use compareware::api::{delete_item, delete_property}; // Import API handlers use std::sync::Arc; use tokio::sync::Mutex; + + panic_hook::init(); // Setup logging std::env::set_var("RUST_LOG", "info"); @@ -167,12 +170,15 @@ pub fn main() { #[cfg(all(not(feature = "ssr"), feature = "csr"))] pub fn main() { + // Initialize custom panic hook for better diagnostics + panic_hook::init(); + // a client-side main function is required for using `trunk serve` // prefer using `cargo leptos serve` instead // to run: `trunk serve --open --features csr` use compareware::app::*; - console_error_panic_hook::set_once(); + // console_error_panic_hook::set_once(); leptos::mount_to_body(App); } \ No newline at end of file diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 0bd1845..2effd46 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1 +1,2 @@ -pub mod leptos_owner; \ No newline at end of file +pub mod leptos_owner; +pub mod panic_hook; \ No newline at end of file diff --git a/src/utils/panic_hook.rs b/src/utils/panic_hook.rs new file mode 100644 index 0000000..39f883a --- /dev/null +++ b/src/utils/panic_hook.rs @@ -0,0 +1,54 @@ +use std::panic; +use leptos::logging::log; +use wasm_bindgen::prelude::*; + +/// Sets up a custom panic hook that provides more context for Leptos owner disposal panics +pub fn set_custom_panic_hook() { + let original_hook = panic::take_hook(); + + panic::set_hook(Box::new(move |panic_info| { + // Call the original hook first + original_hook(panic_info); + + // Extract panic message + let message = if let Some(s) = panic_info.payload().downcast_ref::() { + s.clone() + } else if let Some(s) = panic_info.payload().downcast_ref::<&str>() { + s.to_string() + } else { + "Unknown panic".to_string() + }; + + // Check if this is an owner disposal panic + if message.contains("OwnerDisposed") { + log!("[PANIC] Leptos owner disposal detected. This usually happens when:"); + log!("[PANIC] 1. A component has been unmounted but JavaScript is still calling into Rust"); + log!("[PANIC] 2. An effect or signal update is running after the component is gone"); + log!("[PANIC] 3. A closure or callback is being called after cleanup"); + + // Log current component registry state + let js_code = r#" + if (window.typeaheadRegistry) { + console.log('[PANIC] Current typeahead registry:', + Object.keys(window.typeaheadRegistry).map(id => ({ + id, + alive: window.typeaheadRegistry[id].alive, + initialized: window.typeaheadRegistry[id].initialized + })) + ); + } else { + console.log('[PANIC] No typeahead registry found'); + } + "#; + + let _ = js_sys::eval(js_code); + } + })); +} + +/// Call in main.rs or app initialization +pub fn init() { + log!("[PANIC_HOOK] Setting up custom panic hook"); + set_custom_panic_hook(); + log!("[PANIC_HOOK] Custom panic hook set up successfully"); +} \ No newline at end of file