leptos-rs/leptos · critical
Tried to access a reactive value that has already been dispo
Error message
Tried to access a reactive value that has already been disposed.
What it means
Accessing a disposed reactive value (signal/memo) in debug builds panics with the definition and access locations. When a reactive owner is disposed (component unmounted or owner dropped), its signals' storage is freed; reading them afterwards is a use-after-free bug in the reactive graph. This is the debug-assertions branch of the disposed-signal panic macro, which includes source locations in the message.
Source
Thrown at reactive_graph/src/traits.rs:75
};
use any_spawner::Executor;
use futures::{Stream, StreamExt};
use std::{
ops::{Deref, DerefMut},
panic::Location,
};
#[doc(hidden)]
/// Provides a sensible panic message for accessing disposed signals.
#[macro_export]
macro_rules! unwrap_signal {
($signal:ident) => {{
#[cfg(any(debug_assertions, leptos_debuginfo))]
let location = std::panic::Location::caller();
|| {
#[cfg(any(debug_assertions, leptos_debuginfo))]
{
panic!(
"{}",
$crate::traits::panic_getting_disposed_signal(
$signal.defined_at(),
location
)
);
}
#[cfg(not(any(debug_assertions, leptos_debuginfo)))]
{
panic!(
"Tried to access a reactive value that has already been \
disposed."
);
}
}
}};
}
View on GitHub (pinned to 32d20f6c9d)
Solutions
- Keep the owning scope alive as long as the value is needed: move signal creation up to a longer-lived owner or use a global store.
- Cancel or guard async work: check ownership before reading (use Owner::with or a generation flag) so callbacks after dispose do nothing.
- Use spawn_local within the component scope and abort/drop pending tasks on cleanup (on_cleanup).
- For data that must outlive components, put it in a global reactive store or context rather than a local signal.
Example fix
// before let (count, set_count) = create_signal(0); setTimeout(move || set_count.set(1), 5000); // may fire after dispose // after let (count, set_count) = create_signal(0); on_cleanup(move || handle.clear_timeout()); // or guard reads with ownership check setTimeout(move || set_count.set(1), 5000);
Defensive patterns
Strategy: try-catch
Validate before calling
// before storing/reading: ensure the owner is alive
if scope_is_active() { set_count.set(new_value); } Type guard
fn signal_alive<T>(s: &ReadSignal<T>) -> bool { !s.try_get().is_none() } // heuristic; prefer ownership checks Try / catch
std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| signal.get())).unwrap_or(default_value)
Prevention
- Cancel timers/fetches in on_cleanup when they touch component-local signals.
- Use spawn_local, not tokio::spawn, for reactive-scope tasks.
- Move long-lived state out of component scopes into stores/context.
When it happens
Trigger: Reading a signal/memo through a closure created by the disposed-signal macro after its owner was disposed — e.g. an async task or timeout callback holding a ReadSignal whose component has unmounted, or an event listener on a node that outlives the signal's scope.
Common situations: setTimeout/setInterval or async fetch resolving after the component unmounted and then set_* / read the signal; storing signals in a global or long-lived struct instead of in the owning scope; manual ownership via Owner::new without keeping the scope alive; hydration/SSR teardown reading client signals.
Related errors
- {location:?} expected context of type {type_name:?} to be pr
- at {location}, the `sandboxed-arenas` feature is active, but
- could not find key for index {index:?} at {caller}
- <FlatRoutes> should not be used with nested routes.
- At {caller}, you call `to_server_error()` or use `server_fn_
AI-assisted analysis of leptos-rs/leptos@32d20f6c9d (2026-09-01).
Data as JSON: /api/errors/d6e9979538f81f71.
Report an issue: GitHub.