The Three Lifecycle Phases
Every component goes through the same three phases: mounting (created and inserted into the DOM for the first time), updating (re-rendered due to new props or state), and unmounting (removed from the DOM).
Lifecycle Phases
| Phase | When It Happens |
|---|---|
| Mounting | The component is rendered for the first time |
| Updating | The component re-renders due to new props, state, or context |
| Unmounting | The component is removed from the DOM entirely |
How useEffect Maps to the Lifecycle
Rather than separate lifecycle methods (as in older class components), useEffect covers all three phases through its dependency array and cleanup function.
Simulating Lifecycle Phases with useEffect
useEffect(() => {
console.log("Mounted, or a dependency changed");
return () => {
console.log("Cleaning up before the next run, or before unmounting");
};
}, [dependency]);Mapping useEffect to Lifecycle Events
| Class Component Method | useEffect Equivalent |
|---|---|
| componentDidMount | useEffect(() => {...}, []) |
| componentDidUpdate | useEffect(() => {...}, [dep]) |
| componentWillUnmount | The cleanup function returned from useEffect |
Class Components Had Explicit Lifecycle Methods
Before hooks, class components used named methods for each phase — componentDidMount, componentDidUpdate, componentWillUnmount — spreading related logic (like subscribing and unsubscribing) across different methods instead of together in one place.
The Old Class Component Approach (For Reference)
class Timer extends React.Component {
componentDidMount() {
this.id = setInterval(() => this.tick(), 1000);
}
componentWillUnmount() {
clearInterval(this.id);
}
tick() {
// ...
}
render() {
return <p>Timer running</p>;
}
}Best Practice
Think in terms of "synchronizing with a dependency" rather than "running code at a lifecycle moment" — it’s a more accurate mental model for how useEffect actually behaves, and helps avoid common effect-related bugs.