Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Defer Execution of Unmount Life Cycle Hooks #6003

Closed
sebmarkbage opened this issue Feb 9, 2016 · 12 comments
Closed

Defer Execution of Unmount Life Cycle Hooks #6003

sebmarkbage opened this issue Feb 9, 2016 · 12 comments

Comments

@sebmarkbage
Copy link
Collaborator

Unmounting can take significant and unbounded time when we traverse the tree and call the life-cycles. This is the smallest problem of incremental reconciliation (out of: mount at arbitrary location > update a boundary > mount a boundary > unmount).

The theory is that we can remove the node from the tree first and then call the life-cycles later on during idle time. This means that the refs won't have access to nodes that are in the document anymore so reading any such information would no longer work.

This problem space might also be related to animations. Exit animations want a component/node to exist in the tree for as long as it needs to finish its animation offscreen, asynchronously get deleted from the tree and eventually get cleaned up by calling the unmount life-cycles.

@eplawless
Copy link

Here's an interesting API direction you might consider:

const Example = React.createClass({
    componentWillMount() {
        const done = this.deferUnmount(); // returns a function, could easily be an id or handle
        this.downloadSomething()
            .then(this.doSomethingWithIt)
            .then(done, done);
    },
    componentWillUnmount() {
        const done = this.deferUnmount();
        this.animateOutSomehow(done);
    },
    ...
});

This example assumes that deferUnmount is available as an instance method during the entire component lifecycle. Components would keep a reference count which causes an unmount some time after it reaches zero. Components would start with a reference count of 1 when created, and it would be decremented when they are no longer rendered by their parent. Calls to deferUnmount would increment the reference count and return an idempotent function which, when called, would decrease the reference count.

In the example, assume that the downloadSomething action from componentWillMount has completed, and the component has been de-parented, which causes componentWillUnmount to be called. A deferUnmount call inside of componentWillUnmount could halt the unmounting process until its returned function is called. I imagine componentWillUnmount would not be called a second time.

I'm not sure whether deferUnmount would defer its entire parent chain from being unmounted, but I suspect that it would.

This would unify the deferred unmount mechanic with normal component lifecycle. It does seem like a potentially dangerous feature, not particularly exception safe, prone to leaks etc. Maybe it could be coupled with a forceUnmount method (or maybe not...) That said, as a tool for building libraries on React I think it would open up a number of opportunities (exit animations being a major use case).

Just a thought. I don't have a good understanding of all of the edge cases around this so I'd love some feedback. I think this would play nicely with #6067 as well.

@RaitoBezarius
Copy link
Contributor

What about returning a Promise in componentWillUnmount, why it would be bad if it is? (we could argue that it creates a dependency on promises in the React library.)

@gersongoulart
Copy link

Sounds like being able to return a Promise in componentWillUnmount() would indeed be a really nice feature.

I'm currently facing an issue where a component needs to clean up a section of the main application state on exit that will be used by the next component that will enter, giving me a "warning.js:25 Warning: There is an internal error in the React performance measurement code. Did not expect componentDidUpdate timer to start while componentWillUnmount timer is still in progress for another instance." and a "Uncaught TypeError: Cannot read property 'apply' of undefined. ReactClass.js:195"

If componentWillUnmount() could wait for the job to complete before proceeding similar to componentWillLeave(callback) from 'react-addons-css-transition-group' (which I'm using to hack around this problem), that'd be terrific.

@gaearon
Copy link
Collaborator

gaearon commented Aug 8, 2016

giving me a "warning.js:25 Warning: There is an internal error in the React performance measurement code. Did not expect componentDidUpdate timer to start while componentWillUnmount timer is still in progress for another instance."

Please file an issue for this. It is a bug, and should be totally solveable.

@guidobouman
Copy link
Contributor

Any updates on this?

@gaearon
Copy link
Collaborator

gaearon commented Jul 26, 2017

If there were, they would be on this issue. 😉

@bricejar
Copy link

Will you leverage CM to solve this issue ?

@gaearon
Copy link
Collaborator

gaearon commented Jul 23, 2020

We’re not planning to do this for classes. However useEffect is already asynchronous, in any mode. Previously the cleanup on unmount was synchronous but we have made it asynchronous on master. This change will go out in React 17. So I think we can close this issue as implemented.

@gaearon gaearon closed this as completed Jul 23, 2020
@frankandrobot
Copy link

@gaearon So the original question was how to defer the execution of the unmount life cycle... for example, to give the component ample time to do an exit animation. I'm not sure I understand how that would be possible with useEffect. Could you maybe elaborate?

@gaearon
Copy link
Collaborator

gaearon commented Nov 27, 2020

Exit animations are a separate problem that we intend to work on in the future. useEffect alone is not a solution to them, although it's an important piece.

@peerreviewsystem
Copy link

peerreviewsystem commented Nov 27, 2020

Thank you @frankandrobot I have the exact same question.

Say I have a Modal component that is displayed according to a boolean.


<>
{
    isVisible && <Modal/>
}
</>

Now, when isVisible becomes falsy, I would like to perform some exit animation on the Modal before the Modal is unmounted. You may say that I could actually pass isVisible as a prop to is Modal so that it can itself control its enter and exit animation.


<>
{
    <Modal isVisible={isVisible} />
}
</>

This is the simple case. The problem is that sometimes this Modal unMount is caused by an entire tree unmounted above it, on which we do not necessarily have control, or that we are not aware of (because it is coded by an other team).

A workaround that I have used is to actually copy the modal dom on unmount and send it to an exit animation context that would perform the animation on a copied DOM node. I think the library framer-motion uses a similar idea to perform animations on dom nodes rendered by different components. The problem is that it is not really reacty, maybe I actually want to perform an exit animation that would involve the children of the Modal. For example, I would like to animate its children first, then animate the modal exit. I am not sure there is a real way to solve this animation problem without complex over-engineering, it is possibly a limitation of the React render model. Right now I just totally gave up on exit animations because I don't want my apps to be too complex.

But yes essentially we would love to be able to defer the unmount of a component, maybe with an imperative api.

useUnMountEffect( () => {
        return function (unMount : Function) =>  {
                 // We can still change the component state here and perform actions.
                 setState({....});
                 dispatch({...});
                unMount(); //  <---- We tell react, now this is the time to unmount ! 
        }
     }        
)

Is there any way we could emulate this behavior ? Somebody mentionned to me that CM would be able to handle such cases but I am not sure how yet.

@peerreviewsystem
Copy link

Exit animations are a separate problem that we intend to work on in the future. useEffect alone is not a solution to them, although it's an important piece.

I sent my message after you but I did not see your answer before submitting. I am happy that the React core team is aware of the feature request and that they will work on it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

11 participants