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.
|
// Full Application structs so names are available in the selected tag list.
|
||||||
let selected: RwSignal<Vec<Application>> = RwSignal::new(vec![]);
|
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! {
|
view! {
|
||||||
<div class="modal-backdrop" on:click=move |_| show_modal.set(false)>
|
<div class="modal-backdrop" on:click=move |_| show_modal.set(false)>
|
||||||
<div class="modal" on:click=move |e| e.stop_propagation()>
|
<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_delete_modal = RwSignal::new(false);
|
||||||
let show_add_app_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,
|
// LocalResource avoids reading the resource outside <Suspense> during hydration,
|
||||||
// which would cause a mismatch between the SSR-rendered fallback and the content
|
// which would cause a mismatch between the SSR-rendered fallback and the content
|
||||||
@@ -309,6 +307,25 @@ pub fn HostDetailPage() -> impl IntoView {
|
|||||||
|
|
||||||
view! {
|
view! {
|
||||||
<div class="host-detail-page">
|
<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> }>
|
<Suspense fallback=|| view! { <p class="empty">"Loading host…"</p> }>
|
||||||
{move || host.get().map(|r| match (*r).clone() {
|
{move || host.get().map(|r| match (*r).clone() {
|
||||||
Err(e) => view! {
|
Err(e) => view! {
|
||||||
@@ -317,7 +334,6 @@ pub fn HostDetailPage() -> impl IntoView {
|
|||||||
|
|
||||||
Ok(detail) => {
|
Ok(detail) => {
|
||||||
let id = detail.id;
|
let id = detail.id;
|
||||||
let modal_name = detail.name.clone();
|
|
||||||
let port_count = detail.ports.len();
|
let port_count = detail.ports.len();
|
||||||
let app_count = detail.applications.len();
|
let app_count = detail.applications.len();
|
||||||
let ports = detail.ports;
|
let ports = detail.ports;
|
||||||
@@ -433,7 +449,7 @@ pub fn HostDetailPage() -> impl IntoView {
|
|||||||
view! {
|
view! {
|
||||||
<option
|
<option
|
||||||
value=n.id.to_string()
|
value=n.id.to_string()
|
||||||
selected=(n.id == current)
|
selected=n.id == current
|
||||||
>
|
>
|
||||||
{label}
|
{label}
|
||||||
</option>
|
</option>
|
||||||
@@ -545,16 +561,6 @@ pub fn HostDetailPage() -> impl IntoView {
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</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 ───────────────────────────────────
|
// ── Danger zone ───────────────────────────────────
|
||||||
<div class="danger-zone">
|
<div class="danger-zone">
|
||||||
<button
|
<button
|
||||||
@@ -565,16 +571,6 @@ pub fn HostDetailPage() -> impl IntoView {
|
|||||||
"Delete host"
|
"Delete host"
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</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()
|
}.into_any()
|
||||||
}
|
}
|
||||||
})}
|
})}
|
||||||
|
|||||||
Reference in New Issue
Block a user