React applications are built from components, and every component lives a life. It appears on the screen. It reacts to new information. Eventually it disappears. Ignore that rhythm and the price is real. Memory leaks creep in. Render performance degrades. Users click buttons that no longer respond because the component attached to them unmounted ten minutes ago but the event listener is still alive. Understanding the component lifecycle is not an academic exercise. It is the foundation of writing software that stays reliable under real use.

Think of a component’s existence in three stages. Developers often describe them as birth, growth, and death. React calls them mounting, updating, and unmounting. Each stage has a specific job, and each gives you a precise moment to run code or clean it up.

The Three Phases

Mounting is birth. The component is created and inserted into the browser DOM for the first time. This is your setup window. If the component needs remote data, this is when you request it. If it needs an initial state calculated from props, you do that here. In the class component era, this was the domain of componentDidMount. With modern functional components, you handle it with useEffect and an empty dependency array. The key detail is that this block of code runs once, immediately after React places the element into the page. That makes it ideal for one-time setup like fetching a user profile or reading a value from localStorage.

Updating is growth. A mounted component does not sit still. Props arrive from a parent. State changes from a user interaction. The parent itself re-renders, forcing the child to follow. React then re-renders the component to keep the UI in sync with your data. This phase repeats many times during a component’s life. Every time a dependency shifts, your effects run again, and the component breathes new output back into the DOM. Because this phase triggers so frequently, it is also where most performance problems start. Unnecessary work here compounds fast.

Unmounting is death. The component leaves the screen, maybe because the user navigated to a different page or a conditional render switched off. At this point, the component is destroyed, but the outside world does not always know that. Timers keep ticking. WebSockets keep listening. Event listeners on the window object stay attached. If you do not clean these up, they hold references to dead components and leak memory. The browser wastes cycles on ghosts. React gives you a cleanup function precisely so you can stop intervals, close connections, and remove listeners before the component vanishes for good.

From Class Methods to Hooks

For years, React engineers managed these phases through class components. Logic was split across distinct methods. You fetched data inside componentDidMount, responded to prop changes inside componentDidUpdate, and swept the floors inside componentWillUnmount. The problem was that related logic ended up scattered. A single feature, like a live chat connection, required code in three separate lifecycle methods just to connect, check for thread changes, and disconnect.

Hooks changed that. The useEffect hook in functional components lets you group logic by purpose rather than by lifecycle phase. Still, the mental model maps cleanly if you know where to look.

To handle mounting, write useEffect(() => { ... }, []). The empty dependency array tells React this effect has no external dependencies, so it runs only after the initial render. This replaces componentDidMount.

To handle updates, include the specific values React should watch: useEffect(() => { ... }, [dependency]). When dependency changes between renders,