35

I've got a may confusing question because it does not fit standard-behaviour how react and the virtual dom works but i would like to know the answer anyway.

Imagine i've got a simple react-component which is called "Container". The Container-component has a "div" inside of the render-method which contains another component called "ChildContainer". The "div" which surrounds the "ChildContainer" has the id "wrappingDiv".

Example:

render() {
  <Container>
    <div id="wrappingDiv">
      <ChildContainer/>
    </div>
  </Container
}

How can i destroy the "ChildContainer"-component-instance and create a completly new one. Which mean the "ComponentWillUnmount" of the old instance is called and the "ComponentDidMount" of the new component is called.

I don't want the old component to update by changing the state or props.

I need this behaviour, because an external library from our partner-company got a libary which change the dom-items and in React i'll get a "Node not found" exception when i Update the component.

Kevin H.
  • 662
  • 1
  • 6
  • 16

2 Answers2

94

If you give the component a key, and change that key when re-rendering, the old component instance will unmount and the new one will mount:

render() {
  ++this.childKey;
  return <Container>
    <div id="wrappingDiv">
      <ChildContainer key={this.childKey}/>
    </div>
  </Container>;
}

The child will have a new key each time, so React will assume it's part of a list and throw away the old one, creating the new one. Any state change in your component that causes it to re-render will force that unmount-and-recreated behavior on the child.

Live Example:

class Container extends React.Component {
  render() {
    return <div>{this.props.children}</div>;
  }
}

class ChildContainer extends React.Component {
  render() {
    return <div>The child container</div>;
  }
  componentDidMount() {
    console.log("componentDidMount");
  }
  componentWillUnmount() {
    console.log("componentWillUnmount");
  }
}

class Example extends React.Component {
  constructor(...args) {
    super(...args);
    this.childKey = 0;
    this.state = {
      something: true
    };
  }

  componentDidMount() {
    let timer = setInterval(() => {
      this.setState(({something}) => ({something: !something}));
    }, 1000);
    setTimeout(() => {
      clearInterval(timer);
      timer = 0;
    }, 10000);
  }
  
  render() {
    ++this.childKey;
    return <Container>
      {this.state.something}
      <div id="wrappingDiv">
        <ChildContainer key={this.childKey}/>
      </div>
    </Container>;
  }
}

ReactDOM.render(
  <Example />,
  document.getElementById("root")
);
<div id="root"></div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.4.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.4.2/umd/react-dom.production.min.js"></script>

Having said that, there may well be a better answer to your underlying issue with the plugin. But the above addresses the question actually asked... :-)

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
9

Using hooks, first create a state variable to hold the key:

const [childKey, setChildKey] = useState(1);

Then use the useEffect hook to update the key on render:

useEffect(() => {
   setChildKey(prev => prev + 1);
});

Note: you probably want something in the array parameter in useEffect to only update the key if a certain state changes

hada
  • 137
  • 2
  • 6