facebook/react

Asynchronous ComponentWillUnmount in React 16

Closed this issue ยท 12 comments

In React 15, ComponentWillUnmount called first before the rendering of next component in DOM. In the current version (v16), ComponentWillUnmount was called after the mounting of next component.

It creates an issue with my existing code, since I reuse the same component after the history changes so it invokes the componentWillMount with new props and change in URL and thereafter, it invokes the ComponentWillUnmount of same component.

Is there still a way to do this in a synchronous way?

In the current version (v16), ComponentWillUnmount was called after the mounting of next component.

This is not exactly right. It's called after the next component is created but before it's mounted.

We can't revert it to the old way because it was incompatible with where we want to take React in the future. We want to enable asynchronous rendering in future versions, but it's impossible if we don't create the new instance early. This is why we made this change.

It's not exactly clear to me what you're trying to do, but perhaps moving that logic from componentWillMount to componentDidMount can help.

To give you more assistance we need to know your use case in detail. Can you create a small fiddle that demonstrates what you were trying to do?

I tried to create a search with react + redux + react-router-dom.

User query will appear in URL after the user tries to search.

So, when the user searches for xyz it will call the new route(search/xyz) from the previous URL (/search).

Check this code.

Due to async behaviour, componentWillMount calling before componentWillUnmount because of rendering same component in both cases.

@aj-anuragjain I hope you don't mind me chiming in with another case in which this change of ordering caused some of my own existing code to not function as expected anymore.

I have an application where I maintain an array that acts like a stack in the application state. I expect parent components to push data onto it before their children do. I'm using componentWillMount to do so. For example, when displaying the following components

<Parent>
   <Child>
      <Grandchild/>
   </Child>
</Parent>

, the resulting application state would resemble ["parent", "child", "grandchild"]. Note that using componentDidMount instead would lead to a reverse ordering, because child components would call componentDidMount first.

Now if a change in route would cause my new displayed components to become

<Parent>
   <Child2>
      <Grandchild2/>
   </Child2>
</Parent>

, in the case of React 15, popping an item off the stack using componentWillUnmount on Child and GrandChild would each remove one item from the list (causing it to temporarily be ["parent"]), after which Child2 and GrandChild2 push() their own names onto the stack, resulting in the correct ["parent", "child2", "grandchild2"].

In React 16, componentWillMount for Child2 and Grandchild2 is called first, resulting in the temporary ["parent", "child", "grandchild", "child2", "grandchild2"] before two pop() operations are called, once again resulting in the undesired ["parent", "child", "grandchild"].

What I'm basically looking for is the tools that allow me to make consecutive application state modifications a) in parent to child order on setup, b) in child to parent order on teardown and c) in order of torn down components first, then newly setup components later.

Alternative solutions are possible for this issue, but it would be great to know if this case somehow fits React's general vision.

@aj-anuragjain @shinrinyoku Have either of you found a clean refactoring to work around this issue?

I'm running into this when hooking into componentWillUnmount of one component to fire a cleanup action/set a flag to reset a part of the Redux state tree before showing another component that also uses that part of the state tree but needs it to be re-initialized before render. The official docs explicitly recommend use of this hook as cleanup, so this will probably break a lot of people unless changed/docs update/some workaround is recommended.

@goodmorninggoaway for the particular use case I mentioned, my current workaround is to simply replace the entire stack on each componentWillMount, instead of pushing and popping individual items on/off it. This means there's no more explicit cleanup/teardown going on, just replacement of state.

It works for me because the order of componentWillMount calls is deterministic, but there's a lot of duplication of functionality and it imposes additional constraints on my components, so I wouldn't call it a clean refactoring by any means.

I'll close this because it works as intended.

In general we recommend against any side effects in componentWillMount, as documented in its API reference. It is likely that there will be more issues with side effects in it in the future.

Regarding componentWillUnmont.

The official docs explicitly recommend use of this hook as cleanup, so this will probably break a lot of people unless changed/docs update/some workaround is recommended.

The docs are talking about clean up on the component level. For example listeners added by a component.

What you're talking about is cleaning up global state. This is inherently messy and can get very complicated. You're welcome to file an issue or PR in the docs repo to clarify it.

fire a cleanup action/set a flag to reset a part of the Redux state tree before showing another component that also uses that part of the state tree but needs it to be re-initialized before render

I've written such code myself before but always regretted it. This is brittle. I recommend refactoring away from patterns where you need this.

The easiest way to do so is to not reuse the same part of the tree in such a way. Think about it: components are generally expected to be independent. What happens if you render two such "exclusive" components at the same time? They will conflict. This doesn't seem like the right component thinking.

I understand it is more code to avoid such patterns, but in my experence replacing a single state tree branch with an object or an array was enough. For example you can key it by ID (even a dynamically generated one), and then instead of tearing down the old one in time for the new component mounting, you can just point the new component to the branch with new ID, while keeping the old one alive. Then eventually you can emit an action that cleans up unused state branches and only leaves the one with the most recent ID.

@gaearon Good idea and seems like a simple refactor, to use something like an object keyed off of an ID (similar to the byId pattern used with Redux normalizing) instead of trying to replace/reuse an array. Thanks for the nudge in the right direction!

This coupled with the 'Cannot have two HTM5 backends at the same time' caused a bit of a problem on a project that I'm working on.

We are using two components that have dependencies on react-dnd
~ react-tag-input
~ react-redux-grid

View 1 - Has several tabs one of which contains react-tag-input
View 2 - Uses react-redux-grid

If the user is on View 1 and clicks to navigate to View 2, we intercept with NavigationPrompt from react-router-navigation-prompt.

In our case, View 2 was rendering before View 1 unmounted, and we got the error described here react-dnd/react-dnd#186 . I looked at the possible solutions, but don't think I can use them for react-redux-grid.

I patched it by setting a state flag in View 2 to not render the component until it was mounted. This seems to work for the time being until we can switch out the react-redux-grid component.

Sorry about this. I would design that part of react-dnd differently today. But I no longer maintain it, alas.

Components that have dependencies on react-dnd shouldn't be including the backend provider into them. It's meant to be supplied by the application developer at the top โ€” not in the components themselves. Please feel free to file issues with react-tag-input and react-redux-grid explaining this. Alternatively, design of that part of react-dnd itself needs changes.