Skip to content

use_effect's unmount method is not always called with dynamically rendered children #1198

Closed

Description

@Archmonger

Current Situation

This issue was first discovered while trying to develop reactive-python/reactpy-django#226

I created a component that simulates a list of conditionally rendered children. Each child in the list contains a unique key. However, the use_effect unmount method does not always appear to be called when the component is dismounted.

Here is a minimum viable example:

from uuid import uuid4

from reactpy import component, hooks, html


@component
def child():
    ownership_uuid = hooks.use_memo(lambda: uuid4().hex)

    @hooks.use_effect(dependencies=[])
    async def unmount_manager():
        print("registering new unmount func")
        def unmount():
            # FIXME: This is not working as expected. Dismount is not always called.
            print("unmount func called")
        return unmount

    return ownership_uuid


@component
def root():
    components = hooks.use_ref([child(key=uuid4().hex)])
    reload_trigger, set_reload_trigger = hooks.use_state(True)

    async def add_front_child(event):
        components.current.insert(0, child(key=uuid4().hex))
        set_reload_trigger(not reload_trigger)

    async def add_end_child(event):
        components.current.append(child(key=uuid4().hex))
        set_reload_trigger(not reload_trigger)

    async def remove_front_child(event):
        if components.current:
            components.current.pop(0)
            set_reload_trigger(not reload_trigger)

    async def remove_end_child(event):
        if components.current:
            components.current.pop()
            set_reload_trigger(not reload_trigger)

    return html.div(
        html.div(
            html.button({"onClick": add_front_child}, "Add End"),
            html.button({"onClick": add_end_child}, "Add Front"),
            html.button({"onClick": remove_front_child}, "Remove End"),
            html.button({"onClick": remove_end_child}, "Remove Front"),
        ),
        html.div(f"Components: {components.current}"),
        components.current,
    )

You can demo this by adding a bunch of children, then removing them. If you look at terminal, you'll notice that unmount was only called once despite multiple children being unloaded.

Proposed Actions

No response

Activity

  1. JamesHutchison commented on Nov 3, 2024

    @JamesHutchison
    Contributor

    This might be related to a bug I've seen where nested children are marked as "rendered" and then if there's subsequent updates to their state they get skipped. Changing the rendering order can expose or hide the bug.

    If I had to guess, an example of this:
    Button alters state on child and triggers callback on parent
    Parent alters parameter passed to child based on callback
    Child doesn't render with new parameter because it was already rendered.

    This can create a case where subsequent triggers to the child fail because the component is unmounted

    I'm writing this off the top of my head, I don't have a reproducible state off hand.

  2. Archmonger commented on Dec 13, 2024

    @Archmonger
    ContributorAuthor

    @JamesHutchison Would you be interested at taking a crack at this issue?

  3. JamesHutchison commented on Dec 13, 2024

    @JamesHutchison
    Contributor

    I don't have any free time unfortunately. I'm also not sure the sure the use case for this. It seems like component rendering shouldn't have side effects. If the cause is the example I referred to, the solution would be rewriting the render logic, which I believe you already intend to do.

  4. added a commit that references this issue on Dec 10, 2025
    51198af
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    priority-0-criticalMust be resolved and released immediately.release-patchWarrents a patch release

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions