`onmounted` lifecycle has no cleanup path causing leaks on element removal
The Problem
Animation engines, visualization controllers, and other systems that manage per-element state need to track element lifecycles. Dioxus provides onmounted to notify when an element enters the DOM, but there's no corresponding mechanism to clean up when an element leaves.
Without cleanup notifications, these systems cannot release state associated with removed elements.
Example
fn MyComponent(show_card: bool) -> Element {
rsx! {
div {
if show_card {
div {
onmounted: |e| animation_engine.track(e.data()),
// ❌ No way to cleanup when this div is removed
"Animated Card"
}
}
}
}
}
When show_card becomes false, the animation engine still thinks the element exists, causing:
- Unbounded state growth - Tracking state grows forever with orphaned entries
- Wasted computation - Loops iterate over stale entries
- Stale references - Calling methods on removed elements
Why use_drop Doesn't Work
use_drop operates at the component scope level, not the element level. When show_card becomes false above, use_drop doesn't fire because MyComponent is still mounted.
Other Frameworks
React, Vue, Svelte, and Solid all provide element-level cleanup mechanisms.
This might even be considered a bug as much as an enhancement due to how essential it is.
onmounted is very similar to ref in react and provides access to the raw mounted dom node.
It looks like react implements this by allowing you to return a cleanup function from the onmounted callback mirroring the useEffect API: https://react.dev/reference/react-dom/components/common#returns.
We don't currently expose the option to return a cleanup callback from use_effect, but we probably should and if we do, keeping the apis similar would be nice.
This could also make the state a bit cleaner in some cases:
div {
onmounted: move |e| {
// We don't need to store the mounted node in a hook since we only use it when the node is mounted or cleaned up
start_animation(e.data());
move || stop_animation(e.data())
},
}
This could also make the state a bit cleaner in some cases
Hey Evan, Indeed! I believe you're right. Returning a cleanup closure from unmounted is a better pattern for a number of reasons. I'll submit revised/new PR's (dioxus and docsite).
@ealmloff, per our conversation above, I've updated this issue and rewritten the PR with this better approach. A new pair of PR's are submitted which I'd apprecaite your eyes on ━ Hopefully done right and of value.
For now I'll use a local build to solve the orphan issues in my project.
PR: #5117 Documentation: https://github.com/DioxusLabs/docsite/pull/613