diff --git a/src/components/typeahead_input.rs b/src/components/typeahead_input.rs index f4d6048..d9f2b45 100644 --- a/src/components/typeahead_input.rs +++ b/src/components/typeahead_input.rs @@ -22,69 +22,133 @@ pub fn TypeaheadInput( ) -> impl IntoView { let (is_initialized, set_initialized) = create_signal(false); + // Create a unique ID for this component instance + let component_id = format!("typeahead-{}", uuid::Uuid::new_v4()); + // Flag to track if component is mounted let is_mounted = Rc::new(RefCell::new(true)); let is_mounted_clone = is_mounted.clone(); - // Cleanup function to run when component is unmounted - on_cleanup(move || { - log!("[CLEANUP] TypeaheadInput component unmounting"); - *is_mounted_clone.borrow_mut() = false; - }); - // Clone necessary values for the async task let fetch_suggestions_clone = fetch_suggestions.clone(); let on_select_clone = on_select.clone(); let node_ref_clone = node_ref.clone(); + let component_id_clone = component_id.clone(); + // Create a cancellation token for the async task + let (cancel_token, set_cancel_token) = create_signal(false); + + // Spawn the initialization task using spawn_local instead of spawn_local_with_handle spawn_local(async move { - log!("[INIT] Component mounted"); + log!("[INIT] Component mounted: {}", component_id_clone); let mut retries = 0; - while retries < 10 { + while retries < 10 && !cancel_token.get() { // Check if component is still mounted before proceeding if !*is_mounted.borrow() { - log!("[INIT] Component unmounted, aborting initialization"); + log!("[INIT] Component unmounted, aborting initialization: {}", component_id_clone); return; } if let Some(input) = node_ref_clone.get() { - log!("[INIT] Input element found"); + log!("[INIT] Input element found: {}", component_id_clone); // Only proceed if component is still mounted - if !*is_mounted.borrow() { - log!("[INIT] Component unmounted after input found, aborting"); + if !*is_mounted.borrow() || cancel_token.get() { + log!("[INIT] Component unmounted after input found, aborting: {}", component_id_clone); return; } - let bloodhound = initialize_bloodhound(fetch_suggestions_clone.clone()); + let bloodhound = initialize_bloodhound(fetch_suggestions_clone.clone(), &component_id_clone); - // Store bloodhound globally - js_sys::Reflect::set( + // Store bloodhound in a component-specific global variable + let bloodhound_var = format!("bloodhoundInstance_{}", component_id_clone.replace("-", "_")); + if let Err(_) = js_sys::Reflect::set( &js_sys::global(), - &"bloodhoundInstance".into(), + &bloodhound_var.into(), &bloodhound - ).unwrap(); + ) { + log!("[ERROR] Failed to store bloodhound instance: {}", component_id_clone); + } // Only proceed if component is still mounted - if !*is_mounted.borrow() { - log!("[INIT] Component unmounted before typeahead init, aborting"); + if !*is_mounted.borrow() || cancel_token.get() { + log!("[INIT] Component unmounted before typeahead init, aborting: {}", component_id_clone); return; } - initialize_typeahead(&input, bloodhound, on_select_clone.clone(), node_ref_clone.clone()); + initialize_typeahead(&input, bloodhound, on_select_clone.clone(), node_ref_clone.clone(), &component_id_clone); // Only set initialized if component is still mounted - if *is_mounted.borrow() { - set_initialized.set(true); + if *is_mounted.borrow() && !cancel_token.get() { + // Use a try_update to safely update the signal + let _ = try_with_owner(Owner::current().unwrap(), move || { + set_initialized.set(true); + }); } break; } + // Check if component is still mounted before sleeping + if !*is_mounted.borrow() || cancel_token.get() { + log!("[INIT] Component unmounted during retry loop, aborting: {}", component_id_clone); + return; + } + gloo_timers::future::sleep(Duration::from_millis(100)).await; retries += 1; } }); + + // Clone component_id for on_cleanup + let component_id_for_cleanup = component_id.clone(); + + // Comprehensive cleanup function + on_cleanup(move || { + log!("[CLEANUP] TypeaheadInput component unmounting: {}", component_id_for_cleanup); + + // Signal the async task to cancel + set_cancel_token.set(true); + + // Mark component as unmounted + *is_mounted_clone.borrow_mut() = false; + + // Clean up component-specific global references + let bloodhound_var = format!("bloodhoundInstance_{}", component_id_for_cleanup.replace("-", "_")); + let prepare_fn_var = format!("bloodhoundPrepare_{}", component_id_for_cleanup.replace("-", "_")); + + let cleanup_script = format!(r#" + try {{ + // Clean up the bloodhound instance + if (window['{bloodhound_var}']) {{ + delete window['{bloodhound_var}']; + }} + + // Clean up the prepare function + if (window['{prepare_fn_var}']) {{ + delete window['{prepare_fn_var}']; + }} + + // Clean up any typeahead instances + if (window.typeaheadCleanupFunctions && window.typeaheadCleanupFunctions['{component_id}']) {{ + window.typeaheadCleanupFunctions['{component_id}'](); + delete window.typeaheadCleanupFunctions['{component_id}']; + }} + + console.log('[JS] Global cleanup completed for {component_id}'); + }} catch (e) {{ + console.error('[JS] Cleanup error for {component_id}:', e); + }} + "#, + bloodhound_var = bloodhound_var, + prepare_fn_var = prepare_fn_var, + component_id = component_id_for_cleanup + ); + + if let Err(e) = js_sys::eval(&cleanup_script) { + log!("[RUST] Cleanup script eval error: {:?}", e); + } + }); // CSS let css = r#" @@ -141,6 +205,11 @@ pub fn TypeaheadInput( } "#; + // Clone component_id for event handlers + let component_id_for_focus = component_id.clone(); + let component_id_for_blur = component_id.clone(); + let component_id_for_input = component_id.clone(); + view! {