dioxus icon indicating copy to clipboard operation
dioxus copied to clipboard

`onmounted` lifecycle has no cleanup path causing leaks on element removal

Open rapporian opened this issue 2 months ago • 4 comments

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.

rapporian avatar Dec 17 '25 05:12 rapporian

This might even be considered a bug as much as an enhancement due to how essential it is.

rapporian avatar Dec 17 '25 06:12 rapporian

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())
    },
}

ealmloff avatar Dec 18 '25 14:12 ealmloff

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).

rapporian avatar Dec 18 '25 22:12 rapporian

@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

rapporian avatar Dec 19 '25 08:12 rapporian