Skip to content

TransitionGroup shouldn't try to setState if it has been unmounted #164

Closed
@craigglennie

Description

@craigglennie

I need to use CSSTransition to help with animating a modal on and off the screen. We're using the portal approach, wherein a new React tree is created on a root node (child of document.body), which allows the modal to take over the whole screen. This is required because we have some elements that contain modals, but which create a new stacking context, which breaks (for our purposes) absolute and fixed positioning. This a common approach for modals.

What I want to do is create the portal DOM node when the modal is displayed, and destroy it when the modal is closed. I don't want to have a permanent portal DOM node mounted at the root (we are transitioning to React, and have majority non-React JS; we want React to be self-contained). My approach is to use the onExited handler of CSSTransition to detect when a Modal has finished animating off the screen, and then unmount the whole React portal and remove it's container from the DOM. This works without error, but I am getting a warning from React:

Warning: setState(...): Can only update a mounted or mounting component. This usually means you called setState() on an unmounted component. This is a no-op. Please check the code for the TransitionGroup component.

I've tracked it down to the handleExited function in TransitionGroup. It's calling my handler function, which is causing the whole React tree to be unmounted, and then it calls setState, triggering the warning. I believe this can be easily fixed by checking whether the TransitionGroup has been unmounted, per this doc.

I have created a JSFiddle to demonstrate the issue here. If you have the console open you will see the warning when you click Close Modal (the fiddle doesn't work repeatedly, so you need to refresh to see the warning again).

My proposal is to change TransitionGroup following the example in the doc I linked above.

componentDidMount() {
  // existing stuff
  this._isMounted = true;
}

componentWillUnmount() {
  this._isMounted = false;
}

handleExited = (key, node, originalHandler) => {
  // existing stuff

 // Add new guard before calling setState
 if (this._isMounted) {
    // existing call to this.setState
 }
}

If it's agreed that this is an issue I'll be happy to submit a PR for it.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions