ReactJS - Cycle of Life
Hello there, future developers of React! Today, we're going to embark on an exciting journey through the life cycle of React components. Don't worry if you're new to programming – I'll be your friendly guide, explaining everything step by step. By the end of this tutorial, you'll have a solid understanding of how React components come to life, grow, and eventually say goodbye. Let's dive in!
What is a Component Life Cycle?
Before we jump into the code, let's talk about what we mean by a "life cycle." Just like we humans go through different stages in life (birth, growth, and, well, you know...), React components also have their own life stages. These stages are called the component life cycle.
Think of a React component as a virtual pet. When you first create it, it's born. Then it grows and changes as you interact with it. Finally, when you don't need it anymore, it goes away. Understanding this life cycle is crucial for creating dynamic and efficient React applications.
The Three Main Phases of a Component's Life
React components go through three main phases:
- Mounting (Birth)
- Updating (Growth)
- Unmounting (Farewell)
Let's explore each of these phases and the methods associated with them.
1. Mounting Phase
This is when our component is born and added to the DOM (Document Object Model – think of it as the family tree of your webpage).
Key methods in this phase:
Method | Description |
---|---|
constructor() | The component's constructor, called before it is mounted |
render() | The method that actually renders the component |
componentDidMount() | Called after the component is mounted to the DOM |
Let's see a simple example:
import React, { Component } from 'react';
class BabyComponent extends Component {
constructor(props) {
super(props);
this.state = { message: "I'm just born!" };
console.log("Constructor: Hello, I'm being created!");
}
componentDidMount() {
console.log("componentDidMount: I'm now in the DOM!");
}
render() {
console.log("Render: I'm being rendered!");
return <h1>{this.state.message}</h1>;
}
}
export default BabyComponent;
In this example, our BabyComponent
goes through its birth phase. The constructor
is called first, setting up the initial state. Then render
is called to create the actual DOM elements. Finally, componentDidMount
is called, signaling that our component is now fully born and added to the DOM.
2. Updating Phase
This phase occurs when a component's state changes or it receives new props.
Key methods in this phase:
Method | Description |
---|---|
shouldComponentUpdate() | Decides if the component should re-render |
render() | Re-renders the component with updated data |
componentDidUpdate() | Called after the component updates |
Let's extend our previous example:
import React, { Component } from 'react';
class GrowingComponent extends Component {
constructor(props) {
super(props);
this.state = { age: 0 };
}
componentDidMount() {
this.ageInterval = setInterval(() => {
this.setState(prevState => ({ age: prevState.age + 1 }));
}, 1000);
}
shouldComponentUpdate(nextProps, nextState) {
return nextState.age % 5 === 0; // Only update every 5 years
}
componentDidUpdate() {
console.log(`I just turned ${this.state.age}!`);
}
render() {
return <h1>I am {this.state.age} years old</h1>;
}
componentWillUnmount() {
clearInterval(this.ageInterval);
}
}
export default GrowingComponent;
In this example, our GrowingComponent
"ages" every second. The shouldComponentUpdate
method ensures we only celebrate birthdays every 5 years (to save on cake, you know). When the component does update, componentDidUpdate
logs a birthday message.
3. Unmounting Phase
This is the farewell phase, when a component is removed from the DOM.
Key method in this phase:
Method | Description |
---|---|
componentWillUnmount() | Called just before the component is unmounted and destroyed |
In our GrowingComponent
example above, we used componentWillUnmount
to clear the interval we set up, preventing any memory leaks.
Working Example of Life Cycle API
Now, let's put it all together in a more complex example. We'll create a simple "Mood Tracker" app that demonstrates all the life cycle methods.
import React, { Component } from 'react';
class MoodTracker extends Component {
constructor(props) {
super(props);
this.state = { mood: 'neutral', intensity: 5 };
console.log('Constructor: Mood Tracker is being created');
}
componentDidMount() {
console.log('ComponentDidMount: Mood Tracker is now in the DOM');
this.moodInterval = setInterval(() => {
const moods = ['happy', 'sad', 'excited', 'nervous', 'neutral'];
const newMood = moods[Math.floor(Math.random() * moods.length)];
this.setState({ mood: newMood });
}, 5000);
}
shouldComponentUpdate(nextProps, nextState) {
return this.state.mood !== nextState.mood;
}
componentDidUpdate() {
console.log(`Mood updated to: ${this.state.mood}`);
}
componentWillUnmount() {
console.log('ComponentWillUnmount: Goodbye Mood Tracker!');
clearInterval(this.moodInterval);
}
handleIntensityChange = (e) => {
this.setState({ intensity: e.target.value });
}
render() {
console.log('Render: Mood Tracker is rendering');
return (
<div>
<h1>Current Mood: {this.state.mood}</h1>
<input
type="range"
min="1"
max="10"
value={this.state.intensity}
onChange={this.handleIntensityChange}
/>
<p>Intensity: {this.state.intensity}</p>
</div>
);
}
}
export default MoodTracker;
This MoodTracker
component demonstrates all the life cycle methods we've discussed. It randomly changes mood every 5 seconds and allows the user to adjust the mood intensity.
Life Cycle API in Expense Manager App
Now, let's consider how we might use these life cycle methods in a practical application like an Expense Manager.
import React, { Component } from 'react';
class ExpenseManager extends Component {
constructor(props) {
super(props);
this.state = { expenses: [], total: 0 };
}
componentDidMount() {
// Simulate fetching expenses from an API
setTimeout(() => {
const fetchedExpenses = [
{ id: 1, description: 'Groceries', amount: 50 },
{ id: 2, description: 'Gas', amount: 30 },
{ id: 3, description: 'Movie tickets', amount: 20 },
];
this.setState({ expenses: fetchedExpenses }, this.calculateTotal);
}, 1000);
}
shouldComponentUpdate(nextProps, nextState) {
// Only update if the total has changed
return this.state.total !== nextState.total;
}
componentDidUpdate() {
console.log(`Total expenses updated: $${this.state.total}`);
}
calculateTotal = () => {
const total = this.state.expenses.reduce((sum, expense) => sum + expense.amount, 0);
this.setState({ total });
}
render() {
return (
<div>
<h1>Expense Manager</h1>
<ul>
{this.state.expenses.map(expense => (
<li key={expense.id}>{expense.description}: ${expense.amount}</li>
))}
</ul>
<h2>Total: ${this.state.total}</h2>
</div>
);
}
}
export default ExpenseManager;
In this ExpenseManager
component:
- We use
componentDidMount
to simulate fetching expense data from an API when the component first loads. -
shouldComponentUpdate
ensures we only re-render when the total changes, not for every small update. -
componentDidUpdate
logs the new total whenever it changes.
By leveraging these life cycle methods, we create a more efficient and responsive expense tracking application.
Understanding the component life cycle is crucial for building efficient React applications. It allows you to control when certain actions occur, optimize performance, and manage resources effectively. Keep practicing with these concepts, and soon you'll be creating complex, dynamic React applications with ease!
Credits: Image by storyset