Skip to content

ReactJS - Component Life Cycle

In React, components go through a lifecycle: mounting (being added to the DOM), updating (re-rendering due to prop or state changes), and unmounting (being removed from the DOM). Historically, class components used specific lifecycle methods (e.g., componentDidMount, componentWillUnmount). In modern React, functional components primarily use the useEffect hook to handle side effects and actions related to these lifecycle phases.

useEffect allows you to perform side effects in your functional components. Common side effects include data fetching, subscriptions, or manually changing the DOM. It runs after every render by default, but you can control when it runs.

Its basic syntax is useEffect(setupFunction, dependenciesArray?): _ setupFunction: The function containing the side effect logic. It can optionally return a cleanupFunction. _ dependenciesArray (optional): An array of values (props or state) that the effect depends on. The effect will only re-run if these values change between renders. If omitted, the effect runs after every render. If an empty array [] is provided, the effect runs only once after the initial render (mount) and the cleanup function runs on unmount.

Simulating Lifecycle Phases with useEffect

Section titled “Simulating Lifecycle Phases with useEffect”
  • Initialization (like constructor): In functional components, initialization of state is done with useState, and refs with useRef. This happens when the component function is first called.
  • Mounting (like componentDidMount): To run an effect only once after the component mounts, provide an empty dependency array [] to useEffect. This is ideal for initial data fetching or setting up subscriptions.
  • Updating (like componentDidUpdate): To run an effect when specific props or state change, include those values in the dependency array. The effect will run after the initial render and after any subsequent render where a dependency has changed.
  • Unmounting (like componentWillUnmount): If the setupFunction in useEffect returns another function (the cleanupFunction), React will run this cleanup function before the component unmounts, and also before re-running the effect due to dependency changes. This is crucial for cleaning up resources like subscriptions or timers to prevent memory leaks.
  • componentWillReceiveProps (Deprecated): In functional components, re-renders due to prop changes are handled automatically. If you need to perform a side effect in response to a prop change, use useEffect with that prop in its dependency array.
  • componentWillMount & componentWillUpdate (Deprecated): These older class lifecycle methods, which ran before rendering, are deprecated. Most of their use cases are covered by running effects after rendering with useEffect, or by initializing state directly in the component body.

Similar to PureComponent or shouldComponentUpdate in class components, React.memo is a higher-order component that memoizes your functional component. If your component renders the same result given the same props, React can skip re-rendering the component by reusing the last rendered result. This can be a useful performance optimization for components that render often with the same props.

Let’s adapt the original example. App is a parent component that updates a number, and Content is a child component that logs messages during its lifecycle phases using useEffect.

import React, { useState, useEffect } from 'react';
// Child Component
function Content({ myNumber }) {
useEffect(() => {
console.log('Content: Component did mount (or myNumber changed for the first time)');
// This effect runs on mount AND when myNumber changes.
// If you only want it on mount, the dependency array would be [].
// If you want it specifically for myNumber updates after mount, you might need a ref to track if it's the first render.
console.log('Content: myNumber is now:', myNumber);
return () => {
// This cleanup runs before the effect runs again (if myNumber changes) OR on unmount.
console.log('Content: Cleanup before next myNumber update or unmount. Current myNumber:', myNumber);
};
}, [myNumber]); // Dependency: effect re-runs if myNumber changes
useEffect(() => {
console.log('Content: Effect for MOUNT only');
return () => {
console.log('Content: Effect for UNMOUNT only - Component will unmount!');
};
}, []); // Empty dependency array: runs only on mount and unmount
return (
<div>
<h3>Received Number: {myNumber}</h3>
</div>
);
}
// Parent Component
function App() {
const [data, setData] = useState(0);
const [showContent, setShowContent] = useState(true);
const setNewNumber = () => {
setData(prevData => prevData + 1);
};
// This simulates the unmounting from the original example
useEffect(() => {
if (data > 2) { // For demonstration, unmount after a few increments
console.log('App: Preparing to unmount Content component in 2 seconds');
setTimeout(() => {
setShowContent(false);
console.log('App: Content component unmounted.');
}, 2000);
}
}, [data]);
return (
<div>
<button onClick={setNewNumber}>INCREMENT</button>
{showContent ? <Content myNumber={data} /> : <p>Content has been unmounted.</p>}
</div>
);
}
export default App;
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App.jsx';
const container = document.getElementById('app');
const root = ReactDOM.createRoot(container);
root.render(<App />);
// The unmounting logic is now handled within App.jsx for this example's purpose.
// Original example had: setTimeout(() => { ReactDOM.unmountComponentAtNode(document.getElementById('app')); }, 10000);
// With React 18, it would be: setTimeout(() => { root.unmount(); }, 10000);
  1. Initial Render: _ Content: Component did mount (or myNumber changed for the first time) _ Content: myNumber is now: 0 _ Content: Effect for MOUNT only
  2. Click INCREMENT (data becomes 1): _ Content: Cleanup before next myNumber update or unmount. Current myNumber: 0 _ Content: Component did mount (or myNumber changed for the first time) (Effect re-runs due to myNumber change) _ Content: myNumber is now: 1
  3. Click INCREMENT again (data becomes 2, then 3 triggers unmount logic): Similar logs for myNumber change to 2. When data becomes 3: _ App: Preparing to unmount Content component in 2 seconds (After 2 seconds, Content unmounts) _ Content: Cleanup before next myNumber update or unmount. Current myNumber: 3 _ Content: Effect for UNMOUNT only - Component will unmount! _ App: Content component unmounted.

Understanding the useEffect dependency array is crucial. It controls when your effects run and clean up, preventing unnecessary operations and bugs. Always ensure your dependency array accurately reflects all values from the component scope that your effect uses.