Skip to content

Conversation

@rapporian
Copy link

@rapporian rapporian commented Dec 19, 2025

Hi @ealmloff, This PR implements the closure strategy discussed. It adds an optional cleanup closure to onmounted handlers. When an element is removed from the DOM, any cleanup closure returned by its onmounted handler is automatically invoked.

Here is how it would be used:

div {
    onmounted: move |e| {
        let el = e.data();
        start_animation(el.clone());
        move || stop_animation(el)
    },
}

Changes

  • onmounted handlers can now return a FnOnce() cleanup closure
  • Cleanup runs before element removal (removereplace_with)
  • Existing handlers returning () or async {} continue to work
  • Web renderer only (desktop/mobile not yet implemented)

Testing

  • Added Playwright test that toggles an element and verifies cleanup is called

Documentation

Copy link
Member

@ealmloff ealmloff left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for working on this! API looks a lot nicer, but I would like to avoid more thread locals and we may need deeper integration into core to track removals.

We would also need support for the webview renderer before this is merged. The code should be fairly similar once the web version is fully working

// Handler returns a cleanup closure
impl<F: FnOnce() + 'static> SpawnIfAsyncWithCleanup<CleanupMarker> for F {
fn spawn_with_cleanup(self) {
MOUNTED_CLEANUP.with(|c| *c.borrow_mut() = Some(Box::new(self)));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of a thread local, can you add the cleanup callback to the mounted event? This variant would need to do something like event.data().set_on_cleanup(self)


// Invoke cleanup closure BEFORE replacing the element
#[cfg(feature = "mounted")]
self.invoke_cleanup(id);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure this will catch all removals. If we generate mutations efficiently, only the parent should be replaced when diffing from:

div {
   button { onmounted }
}

To:

p { "node removed" }

The virtual dom knows which nodes are recycled, but it doesn't currently show up in mutations. We could add a new FreeId mutation which just drops the reference from the js heap instead of relying on the id being re-used later. That would also allow us to garbage collect dom nodes earlier in general

Implements ealmloff's PR DioxusLabs#5117 feedback:
1. Store cleanup on MountedData via event.data().set_on_cleanup(closure)
2. Add FreeId mutation emitted when element IDs are freed

Changes:
- Add FreeId variant to Mutation enum with #[non_exhaustive]
- Add free_id() to WriteMutations with default no-op for compatibility
- Add set_on_cleanup()/take_cleanup() methods to MountedData
- Add ListenerCallback::new_raw() for custom event downcast logic
- Add Event::with_data() to create events with different data
- Implement cleanup invocation in web and desktop renderers
- Accept both MountedData and PlatformEventData in onmounted handler

Bbackward compatible for app developers.
Custom renderers get default no-op for free_id().
@rapporian
Copy link
Author

@ealmloff thanks for the feedback and guidance on this. Please see this new checkin where I've tried to achieve what you requested but honestly, it was a step beyond my current depth of understanding of the Dioxus framework. That said, I needed "a little → a lot" of claude-code help.

I came at it from different angles to test it and ensure it was backward compatible "for the app developer", but it needs eyes on it. Thanks for being kind if it's terrible.

Note: The #[non_exhaustive] attribute on Mutation means custom renderers with exhaustive matches will need to add a wildcard. The free_id() trait method has a default no-op, so no implementation is required unless cleanup support is desired. Not sure how you would ideally handle this.

It solves the problem for animation engine development I'm tinkering with.

@rapporian rapporian requested a review from ealmloff December 20, 2025 04:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants