fix(host-detail): move modals outside Suspense and auto-close Effect to parent
Modals rendered inside <Suspense> were unmounted each time the host resource re-fetched, killing their reactive subscriptions and preventing them from reopening. Moving them to the <div> level above <Suspense> keeps them alive across re-fetches. The auto-close Effect for the add-app modal is also moved from AddAppModal to HostDetailPage so it is never recreated across open/close cycles, avoiding the stale-value re-trigger bug. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -39,19 +39,6 @@ fn AddAppModal(
|
||||
// Full Application structs so names are available in the selected tag list.
|
||||
let selected: RwSignal<Vec<Application>> = RwSignal::new(vec![]);
|
||||
|
||||
// Close the modal when the action transitions from in-flight → completed with Ok.
|
||||
// Tracking the pending→false transition (rather than watching value directly) avoids
|
||||
// closing the modal on mount when value still holds a previous session's Ok result.
|
||||
Effect::new(move |was_pending: Option<bool>| {
|
||||
let is_pending = add_action.pending().get();
|
||||
if was_pending == Some(true) && !is_pending {
|
||||
if let Some(Ok(_)) = add_action.value().get() {
|
||||
show_modal.set(false);
|
||||
}
|
||||
}
|
||||
is_pending
|
||||
});
|
||||
|
||||
view! {
|
||||
<div class="modal-backdrop" on:click=move |_| show_modal.set(false)>
|
||||
<div class="modal" on:click=move |e| e.stop_propagation()>
|
||||
@@ -258,6 +245,17 @@ pub fn HostDetailPage() -> impl IntoView {
|
||||
let show_delete_modal = RwSignal::new(false);
|
||||
let show_add_app_modal = RwSignal::new(false);
|
||||
|
||||
// Auto-close the add-app modal when the action completes successfully.
|
||||
// Lives here (not inside AddAppModal) so it is never recreated across modal open/close cycles.
|
||||
Effect::new(move |was_pending: Option<bool>| {
|
||||
let is_pending = add_app_action.pending().get();
|
||||
if was_pending == Some(true) && !is_pending {
|
||||
if let Some(Ok(_)) = add_app_action.value().get() {
|
||||
show_add_app_modal.set(false);
|
||||
}
|
||||
}
|
||||
is_pending
|
||||
});
|
||||
|
||||
// LocalResource avoids reading the resource outside <Suspense> during hydration,
|
||||
// which would cause a mismatch between the SSR-rendered fallback and the content
|
||||
@@ -309,6 +307,25 @@ pub fn HostDetailPage() -> impl IntoView {
|
||||
|
||||
view! {
|
||||
<div class="host-detail-page">
|
||||
// Modals live OUTSIDE <Suspense> so they are not unmounted when the
|
||||
// host resource re-fetches (which would kill their reactive subscriptions).
|
||||
{move || show_add_app_modal.get().then(|| view! {
|
||||
<AddAppModal
|
||||
host_id=host_id()
|
||||
available_apps_res=available_apps_res
|
||||
add_action=add_app_action
|
||||
show_modal=show_add_app_modal
|
||||
/>
|
||||
})}
|
||||
{move || show_delete_modal.get().then(|| view! {
|
||||
<DeleteModal
|
||||
host_name=name_sig.get()
|
||||
delete_action=delete_action
|
||||
host_id=host_id()
|
||||
show_modal=show_delete_modal
|
||||
/>
|
||||
})}
|
||||
|
||||
<Suspense fallback=|| view! { <p class="empty">"Loading host…"</p> }>
|
||||
{move || host.get().map(|r| match (*r).clone() {
|
||||
Err(e) => view! {
|
||||
@@ -317,7 +334,6 @@ pub fn HostDetailPage() -> impl IntoView {
|
||||
|
||||
Ok(detail) => {
|
||||
let id = detail.id;
|
||||
let modal_name = detail.name.clone();
|
||||
let port_count = detail.ports.len();
|
||||
let app_count = detail.applications.len();
|
||||
let ports = detail.ports;
|
||||
@@ -433,7 +449,7 @@ pub fn HostDetailPage() -> impl IntoView {
|
||||
view! {
|
||||
<option
|
||||
value=n.id.to_string()
|
||||
selected=(n.id == current)
|
||||
selected=n.id == current
|
||||
>
|
||||
{label}
|
||||
</option>
|
||||
@@ -545,16 +561,6 @@ pub fn HostDetailPage() -> impl IntoView {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
// ── Add applications modal ────────────────────────
|
||||
{move || show_add_app_modal.get().then(|| view! {
|
||||
<AddAppModal
|
||||
host_id=id
|
||||
available_apps_res=available_apps_res
|
||||
add_action=add_app_action
|
||||
show_modal=show_add_app_modal
|
||||
/>
|
||||
})}
|
||||
|
||||
// ── Danger zone ───────────────────────────────────
|
||||
<div class="danger-zone">
|
||||
<button
|
||||
@@ -565,16 +571,6 @@ pub fn HostDetailPage() -> impl IntoView {
|
||||
"Delete host"
|
||||
</button>
|
||||
</div>
|
||||
|
||||
// ── Delete modal (conditional) ────────────────────
|
||||
{move || show_delete_modal.get().then(|| view! {
|
||||
<DeleteModal
|
||||
host_name=modal_name.clone()
|
||||
delete_action=delete_action
|
||||
host_id=id
|
||||
show_modal=show_delete_modal
|
||||
/>
|
||||
})}
|
||||
}.into_any()
|
||||
}
|
||||
})}
|
||||
|
||||
Reference in New Issue
Block a user