componentWillUnmount fires exactly once, right before React actually removes the component from the DOM and destroys its instance — this is the standard place in a class component to clean up anything set up earlier, typically in componentDidMount, that would otherwise keep running or holding onto resources after the component no longer exists, like an active timer, a subscription, or a manually-attached event listener. In functional components, the equivalent cleanup is handled by returning a cleanup function from useEffect, which React calls automatically when the component unmounts.
1Understanding ComponentWillUnmount
componentWillUnmount fires exactly once, right before React actually removes the component from the DOM and destroys its instance — this is the standard place in a class component to clean up anything set up earlier, typically in componentDidMount, that would otherwise keep running or holding onto resources after the component no longer exists, like an active timer, a subscription, or a manually-attached event listener. In functional components, the equivalent cleanup is handled by returning a cleanup function from useEffect, which React calls automatically when the component unmounts.
Anything started in componentDidMount that persists over time, like setInterval, a WebSocket connection, or a manually-added event listener, needs a matching teardown call in componentWillUnmount — otherwise it keeps running after the component is gone, a classic source of memory leaks and setState-called-on-an-unmounted-component warnings.
class Timer extends React.Component {
state = { seconds: 0 };
componentDidMount() {
this.intervalId = setInterval(() => {
this.setState(prev => ({ seconds: prev.seconds + 1 }));
}, 1000);
}
componentWillUnmount() {
clearInterval(this.intervalId);
}
render() {
return <p>Seconds: {this.state.seconds}</p>;
}
}2Practical Example
Here is a real-world application of ComponentWillUnmount showing how it is used in production React code.
class ChatRoom extends React.Component {
componentDidMount() {
connection.subscribe(this.props.roomId, this.handleMessage);
}
componentWillUnmount() {
connection.unsubscribe(this.props.roomId, this.handleMessage);
}
handleMessage = (msg) => console.log('New message:', msg);
render() {
return <p>Connected to {this.props.roomId}</p>;
}
}3Best Practices
Follow these guidelines when working with ComponentWillUnmount:
1. Pair every subscription, timer, or manually-added listener set up in componentDidMount with a matching teardown call in componentWillUnmount
2. Guard any asynchronous callback that might resolve after unmount, like a fetch response, from calling setState on an already-unmounted component, since that produces a runtime warning
3. Use the functional-component equivalent, returning a cleanup function from useEffect, when writing new code rather than a class-based componentWillUnmount
Tip: Anything started in componentDidMount that persists over time, like setInterval, a WebSocket connection, or a manually-added event listener, needs a matching teardown call in componentWillUnmount — otherwise it keeps running after the component is gone, a classic source of memory leaks and setState-called-on-an-unmounted-component warnings.
class Timer extends React.Component {
state = { seconds: 0 };
componentDidMount() {
this.intervalId = setInterval(() => {
this.setState(prev => ({ seconds: prev.seconds + 1 }));
}, 1000);
}
componentWillUnmount() {
clearInterval(this.intervalId);
}
render() {
return <p>Seconds: {this.state.seconds}</p>;
}
}