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

componentDidUnmount functionality (in addition to componentWillUnmount) #6424

Closed
hadjiada opened this issue Apr 6, 2016 · 19 comments
Closed

Comments

@hadjiada
Copy link

hadjiada commented Apr 6, 2016

It would be great if it was possible to run some code after the component actually unmounted. This is useful where you consider the following:

component Parent {
    has service Service
    has child Child

    componentWillUnmount: {
         destroys Service
    }

    render: {
         Service gets passed to Child as prop
    }
}

component Child {
    componentWillMount: {
         starts listening to Service
    }

    componentWillUnmount: {
         stops listening to Service
    }
}

The above will throw exception during Child componentWillUnmount since Parent gets unmounted first so Service is already destroyed.

If there existed componentDidUnmount the Service can be destroyed after the children are unmounted, i.e.

component Parent {
    has service Service
    has child Child

    componentDidUnmount: {
         destroys Service
    }

    render: {
         Service gets passed to Child as prop
    }
}

component Child {
    componentWillMount: {
         starts listening to Service
    }

    componentWillUnmount: {
         stops listening to Service
    }
}

@jimfb
Copy link
Contributor

jimfb commented Apr 6, 2016

That is an interesting use case, but I'm not yet convinced that it justifies an additional lifecycle method, because I think there is an equally viable alternative for your service deconstruction. I'm curious if you would agree...

What if a Service (as created by the parent) receives the information needed to open the required connection/resource, but doesn't actually open the resource until a child component starts listening. The service then closes the connection/resource when the last child stops listening. This means that the parent never needs to destroy the service, and normal garbage collection rules can apply. Would this approach work for you?

@hadjiada
Copy link
Author

hadjiada commented Apr 6, 2016

It's not really a Service per se, it's any object we use that follows the Observer pattern. It's just it would be good to have componentDidUnmount because then if Parent doesn't destroy the service then you're relying on Children to stop listening properly, which may cause leaks.

@jimfb
Copy link
Contributor

jimfb commented Apr 6, 2016

I think my previous comment above still holds true for most implementations of the Observer pattern. Just :%s/service/observable/g

If children don't stop listening properly, you've likely got bigger problems. It is common for data to be provided by a static parent and children to enter/exit throughout the lifetime of the application, so you'll likely be leaking anyway.

@mr-mig
Copy link

mr-mig commented Apr 7, 2016

AFAIK, without a symmetrical hook for server-side rendering it is impossible to release any resources (unsubscribe listeners) used in componentWillMount.

@gaearon
Copy link
Collaborator

gaearon commented Apr 7, 2016

AFAIK, without a symmetrical hook for server-side rendering it is impossible to release any resources (unsubscribe listeners) used in componentWillMount.

Isn’t the current recommendation to never hold onto any resources in componentWillMount and do this in componentDidMount (which has a symmetric componentWillUnmount to free them up)?

@gaearon
Copy link
Collaborator

gaearon commented Apr 14, 2016

By the way I’m interested in having this method too.

When a child is subscribed to a Flux store, and the parent fires an action in componentWillUnmount to clean up the store state, the child has to include special code for existence of this slice of state, or it would fail to retrieve it (potentially with a null reference).

The child doesn’t know that it should unsubscribe because the parent is cleaning up. There is no way for the parent to safely clean up any external state the child may depend upon. If we had componentDidUnmount, the parent would be given this opportunity.

@hadjiada
Copy link
Author

Yeah at the moment I had to include a bit ugly workarounds for the cases where componentDidUnmount would help

@jimfb
Copy link
Contributor

jimfb commented Apr 15, 2016

@gaearon

The child doesn’t know that it should unsubscribe because the parent is cleaning up. There is no way for the parent to safely clean up any external state the child may depend upon. If we had componentDidUnmount, the parent would be given this opportunity.

@gaearon Yes, that was the original post/request almost verbatim. But is there a reason the approach in #6424 (comment) is insufficient? Seems like a parent can always create an object that lazy-initializes any required resource, and cleans it up when all users of those resources are done. Is it just that there is a little additional boilerplate? Is there a particular resource you have in mind that would exemplify the use case?

@gaearon
Copy link
Collaborator

gaearon commented Apr 15, 2016

Is there a particular resource you have in mind that would exemplify the use case?

In this case, I’m referring to some piece of data in a Flux store that the parent cleans up. I don’t use this pattern myself but some consumers of React Redux bindings do, and I recently introduced a regression related to this use case (reduxjs/react-redux#351).

I wish I could tell people to use a different lifecycle hook for tearing down the data rather than being careful in the container implementation and having to implement workarounds like this: reduxjs/react-redux@ea53d6f. Asking them to pass something like onUnmount also feels unnatural, especially considering that connected children can be deeply in the tree.

@jimfb
Copy link
Contributor

jimfb commented Apr 15, 2016

cc @sebmarkbage

@gaearon
Copy link
Collaborator

gaearon commented Apr 16, 2016

By the way I briefly chatted with @sebmarkbage today about this, and my particular problem would be solved by delaying some calls until render() or transactional setState() callback. I didn’t use this pattern because setState() itself is somewhat inefficient inside a large list, but this is a separate issue.

@hadjiada
Copy link
Author

hadjiada commented Apr 18, 2016

@jimfb

Seems like a parent can always create an object that lazy-initializes any required resource, and cleans it up when all users of those resources are done.

Unless the object cleans itself up (not ideal, and sometimes not available as an option, e.g. parent provides set of listeners (children), then listeners get unmounted, then parent provides another set of listeners), the parent can't clean the object up because the parent gets unmounted before the children.

Switching to Flux/Redux etc. is not for everyone.

@jimfb
Copy link
Contributor

jimfb commented Apr 18, 2016

Unless the object cleans itself up (not ideal, and sometimes not available as an option

Why is it not ideal? When is it not an option (I'm fairly certain it is always an option, because you can use simple reference counting to determine when to cleanup)?

e.g. parent provides set of listeners (children), then listeners get unmounted, then parent provides another set of listeners), the parent can't clean the object up because the parent gets unmounted before the children.

Why does the parent need to do any cleanup? Why doesn't the datastore cleanup when the last child unmounts?

@reem
Copy link

reem commented Mar 31, 2017

@jimfb it's not ideal because you are basically re-encoding the logic for unmounting the tree of components from the deepest child up in your data type, which is just unnecessary error prone boilerplate.

I want to clean up my component after my children have unmounted - in principle this seems like a very reasonable thing to want to do, and I don't think it's reasonable to have to rebuild this lifecycle method externally.

Additionally, you do not always have the option of rewriting the data store - sometimes you are interfacing with legacy code! (This is my current personal situation)

@dimik
Copy link

dimik commented Aug 12, 2017

We also need this method to be implemented

@damianobarbati
Copy link

damianobarbati commented Nov 3, 2017

mega +1 for this one, the problem is perfectly explained by @gaearon.

The child doesn’t know that it should unsubscribe because the parent is cleaning up. There is no way for the parent to safely clean up any external state the child may depend upon. If we had componentDidUnmount, the parent would be given this opportunity.  

@fpw23
Copy link

fpw23 commented Dec 14, 2017

I think I found a hacky way to do this by abusing the ref prop. When the ref prop is passed a function it calls this function passing you a ref to the dom element on mount and passing you null on unmount. By hijacking this function I was able to build a wrapper component that renders its children in a div and then an extra span/div element immediately after the children. The extra span/div has a ref prop set to a function that will bubble up to its parent if its called with a null (meaning the element has been unmounted). I then use this wrapper to sit between my parent and children components, using its onUnmount event in the parent to let me know after the children are gone so I can do the cleanup I need.

here is a link to an example and below is the code:

import React from 'react'

class ComponentChild extends React.Component {
 
  componentWillUnmount() {
    //console.log('Child Props', this.props)
    const { label } = this.props 
    console.log('Unmonuting ' + label)
  }
  
  render () {
   const { label } = this.props 
   return <h2>{label}</h2>
   }
  
}

class UnloadWrapper extends React.Component {
  constructor (props) {
    super(props)
    this.onUnload = this.onUnload.bind(this)
  }
  
  
  onUnload(c) {
    if (!c) {
     console.log('Children Unloaded!')
     const { onUnload } = this.props
     if (onUnload) {
        onUnload() 
     }
    }
  }
  
  render () {
   const { children } = this.props 
   return (
     <div>
       {children}
       <span ref={this.onUnload} />
     </div>
   )
 }
  
}

class ComponentParent extends React.Component {
  constructor(props) {
   super(props)
   this.state = {
      hasChildren: false 
   }
  }
  
  
  render () {
      return (
        <div>
          <button type="button" onClick={() => { this.setState({ hasChildren: !this.state.hasChildren })}}>Switch</button>
          {this.state.hasChildren ? 
          <UnloadWrapper onUnload={this.onUnload}>
            <ComponentChild label="Child 1" />
            <ComponentChild label="Child 2" />
            <ComponentChild label="Child 3" />
          </UnloadWrapper> : null }
        </div>
      ) 
  }
}

class Root extends React.Component {
   render () {
    return (
      <ComponentParent /> 
    )
   }
  
}

export default Root


In my example code, the parent renders a button and a wrapped set of children that are mounted and unmounted on click. When the unmount happens you will see in the console that all three children's componentWillUnmount are called before the unload wrapper calls back to the parent, thus letting the parent know after its children have been fully removed.

So far this works but have not tested fully. My only concern is the order of the unmount sequence react does, I am assuming that on unmount, react processes each element, then loops children in order sequentially, which allows my unload wrapper to work. If it does anything an parallel then I might run into issues. If anybody can see if any major flaws in this please let me know!

@EyalPerry
Copy link

The existing componentWillUnmount lifecycle method is called for the parent first, and only then for each of it's children.

This makes it hard to dispose of shared resources created by the parent and used by the children, since the resources may still be in use by the children when the parent's cWU method is invoked.

@necolas
Copy link
Contributor

necolas commented Jan 9, 2020

Closing as React's design is moving away from class-based lifecycle methods

@necolas necolas closed this as completed Jan 9, 2020
hasref added a commit to hasref/curriculum that referenced this issue Jun 13, 2021
React does not seem to have a `componentDidUnmount` lifecycle method and there is no mention of it in the react docs. As far as I can tell, this was meant to be `componentWillUnmount`. See also this issue on the react github: facebook/react#6424
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

10 participants