Search code examples
reactjsreact-lifecycle

componentWillUnmount method not called when react Component disapears


I have a problem understanding why in some cases componentWillUnmount method is not called even if the component I would expect is unmounted.

To be more concrete, this is the example. Let's consider a Parent component with a button and 2 children: Child_Odd and Child_Even. Child_Odd is "shown" if the number of clicks of the button is odd, otherwise Child_Even is "shown".

I would expect to see the the componentWillUnmount method to be called when the component "disappears", but on the contrary this does not happen. Here a stackblitz reproducing the case (this stackblitz includes also a similar case where the componentWillUnmount method is actually called).

This is the relevant code

export class ChildFlipped extends React.Component {
  render() {
    return (
      <div>
        {this.props.name} - No of clicks ({this.props.numberOfClicks})
      </div>
    );
  }
}

export class ParentFlips extends React.Component {
  constructor(props: any) {
    super(props);
    this.state = {
      clickCounter: 0,
    };
  }
  render() {
    return (
      <div>
        <button
          onClick={() => this.updateState()}
        >
          Click me
        </button>
        {this.state.clickCounter % 2 ? (
          <ChildFlipped
            name={"Even"}
            numberOfClicks={this.state.clickCounter}
          ></ChildFlipped>
        ) : (
          <ChildFlipped
            name={"Odd"}
            numberOfClicks={this.state.clickCounter}
          ></ChildFlipped>
        )}
      </div>
    );
  }
  updateState() {
    this.setState((prevState, _props) => ({
      clickCounter: prevState.clickCounter + 1,
    }));
  }
}

Solution

  • It doesn't fire because the component remains mounted. With respect to reconciliation, the conditional expression

    this.state.clickCounter % 2
    ? <ChildFlipped name={"Even"} numberOfClicks={this.state.clickCounter}/>
    : <ChildFlipped name={"Odd"} numberOfClicks={this.state.clickCounter}/>
    

    is indistinguishable from

    <ChildFlipped name={this.state.clickCounter % 2? "Even" : "Odd"} 
                  numberOfClicks={this.state.clickCounter}/>
    

    since both clauses refer to the same ChildFlipped component. If you add keys, you can make them distinguishable and trigger unmounting:

    his.state.clickCounter % 2
    ? <ChildFlipped key='even' name={"Even"} numberOfClicks={this.state.clickCounter}/>
    : <ChildFlipped key='odd' name={"Odd"} numberOfClicks={this.state.clickCounter}/>
    

    For experimenting with lifecycle methods I built a React lifecycle visualizer (StackBlitz) which may be useful for you.