- Published on
React.js Component Lifecycle Guide
- Authors
- Name
- Muhamad Riyan
- @muhamad-riyan
React Component Lifecycle Phases
Class Components Lifecycle
class ExampleComponent extends React.Component {
// Mounting Phase
constructor(props) {
super(props);
this.state = { count: 0 };
console.log('1. Constructor called');
}
static getDerivedStateFromProps(props, state) {
console.log('2. getDerivedStateFromProps called');
return null;
}
componentDidMount() {
console.log('4. componentDidMount called');
// Good for: API calls, subscriptions, DOM manipulations
}
// Updating Phase
shouldComponentUpdate(nextProps, nextState) {
console.log('5. shouldComponentUpdate called');
return true;
}
getSnapshotBeforeUpdate(prevProps, prevState) {
console.log('6. getSnapshotBeforeUpdate called');
return null;
}
componentDidUpdate(prevProps, prevState, snapshot) {
console.log('7. componentDidUpdate called');
// Good for: Network requests based on prop changes
}
// Unmounting Phase
componentWillUnmount() {
console.log('8. componentWillUnmount called');
// Good for: Cleanup (canceling network requests, subscriptions)
}
render() {
console.log('3. Render called');
return <div>Component Content</div>;
}
}
Functional Components with Hooks
import React, { useState, useEffect, useLayoutEffect } from 'react';
function ExampleFunctionalComponent({ data }) {
// State initialization (similar to constructor)
const [count, setCount] = useState(0);
// Runs after every render
useEffect(() => {
console.log('Component did mount/update');
// Cleanup function (runs before next effect or unmount)
return () => {
console.log('Cleanup - similar to componentWillUnmount');
};
});
// Runs only on mount (empty dependency array)
useEffect(() => {
console.log('Component did mount');
return () => {
console.log('Component will unmount');
};
}, []);
// Runs when 'data' prop changes
useEffect(() => {
console.log('Data changed:', data);
}, [data]);
// Runs synchronously after DOM mutations
useLayoutEffect(() => {
console.log('DOM mutations completed');
});
return <div>Count: {count}</div>;
}
Lifecycle Methods vs Hooks Comparison
// Class Component Lifecycle
class ClassComponent extends React.Component {
constructor(props) {
super(props);
this.state = { data: null };
}
componentDidMount() {
fetchData();
}
componentDidUpdate(prevProps) {
if (prevProps.id !== this.props.id) {
fetchData();
}
}
componentWillUnmount() {
cleanup();
}
}
// Equivalent Functional Component with Hooks
function FunctionalComponent({ id }) {
const [data, setData] = useState(null);
useEffect(() => {
fetchData();
return () => cleanup();
}, [id]);
}
Common Use Cases and Best Practices
Data Fetching
function DataFetchingComponent({ id }) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
let mounted = true;
async function fetchData() {
try {
setLoading(true);
const response = await fetch(`/api/data/${id}`);
const result = await response.json();
if (mounted) {
setData(result);
setError(null);
}
} catch (err) {
if (mounted) {
setError(err.message);
}
} finally {
if (mounted) {
setLoading(false);
}
}
}
fetchData();
return () => {
mounted = false;
};
}, [id]);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
return <div>Data: {JSON.stringify(data)}</div>;
}
Event Listeners
function EventListenerComponent() {
useEffect(() => {
function handleScroll() {
console.log('Window scrolled');
}
window.addEventListener('scroll', handleScroll);
return () => {
window.removeEventListener('scroll', handleScroll);
};
}, []);
return <div style={{ height: '200vh' }}>Scroll me</div>;
}
Subscription Management
function SubscriptionComponent({ channelId }) {
useEffect(() => {
const subscription = subscribeToChannel(channelId, {
onMessage: (message) => {
console.log('New message:', message);
},
onError: (error) => {
console.error('Subscription error:', error);
}
});
return () => {
subscription.unsubscribe();
};
}, [channelId]);
}
Common Lifecycle Patterns
Conditional Fetching
function ConditionalFetchComponent({ shouldFetch, id }) {
useEffect(() => {
if (!shouldFetch) return;
const fetchData = async () => {
// Fetch logic
};
fetchData();
}, [shouldFetch, id]);
}
Debounced Updates
function DebouncedComponent({ searchTerm }) {
useEffect(() => {
const timeoutId = setTimeout(() => {
// Perform search
console.log('Searching for:', searchTerm);
}, 500);
return () => clearTimeout(timeoutId);
}, [searchTerm]);
}
Performance Optimization
Using Memo
const MemoizedComponent = React.memo(function MyComponent({ data }) {
return <div>{data.text}</div>;
}, (prevProps, nextProps) => {
return prevProps.data.id === nextProps.data.id;
});
Callback Memoization
function CallbackComponent() {
const [count, setCount] = useState(0);
const memoizedCallback = useCallback(() => {
console.log('Count:', count);
}, [count]);
return (
<ExpensiveChild onCallback={memoizedCallback} />
);
}
Error Handling
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
console.error('Error caught:', error, errorInfo);
}
render() {
if (this.state.hasError) {
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
Custom Hooks for Lifecycle Management
function useMount(callback) {
useEffect(() => {
callback();
}, []);
}
function useUnmount(callback) {
useEffect(() => {
return () => callback();
}, []);
}
function useUpdateEffect(callback, dependencies) {
const firstRenderRef = useRef(true);
useEffect(() => {
if (firstRenderRef.current) {
firstRenderRef.current = false;
return;
}
return callback();
}, dependencies);
}
Testing Lifecycle Methods
import { render, act } from '@testing-library/react';
test('component lifecycle', async () => {
const { rerender, unmount } = render(<MyComponent id={1} />);
// Test mount
expect(screen.getByText('Initial State')).toBeInTheDocument();
// Test update
await act(async () => {
rerender(<MyComponent id={2} />);
});
// Test unmount
unmount();
});
Remember:
- Use
useEffect
for side effects - Clean up subscriptions and listeners
- Handle async operations properly
- Consider performance implications
- Test lifecycle behavior thoroughly
Learning Resources
Official Documentation
Online Courses & Tutorials
- React Lifecycle Methods - A Deep Dive
- Understanding React's useEffect
- Kent C. Dodds Blog - useEffect Complete Guide