Learn what side effects are in React components and when to use them.
In React, side effects are operations that affect something outside the scope of the current function. These include data fetching, subscriptions, manual DOM manipulations, logging, and other operations that don't directly relate to rendering but are necessary for your application to work correctly.
Examples of side effects in React components include:
React provides the useEffect hook specifically for handling side effects in functional components. This hook lets you perform side effects in a controlled and predictable way.
Master the techniques of working with side effects in React components.
The useEffect hook is the primary way to implement side effects in React functional components. It takes two arguments: a function that contains the side effect code, and an optional dependency array.
The basic syntax of useEffect is:
import React, { useEffect } from 'react';
function MyComponent() {
useEffect(() => {
// Side effect code goes here
// Optional cleanup function
return () => {
// Cleanup code goes here
};
}, [/* dependency array */]);
return (
// JSX for rendering
);
}
The dependency array determines when the effect runs:
Learn how to trigger side effects based on state and prop changes.
One of the most powerful features of useEffect is the ability to selectively run side effects when specific values change. This is achieved through the dependency array, which tells React to only re-run the effect when the listed values have changed.
Here's an example where an effect runs whenever a search query changes:
import React, { useState, useEffect } from 'react';
function SearchResults() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
useEffect(() => {
// Skip the effect on first render with empty query
if (query === '') return;
console.log(`Searching for: ${query}`);
// Fetch search results based on query
const fetchResults = async () => {
try {
const response = await fetch(`https://api.example.com/search?q=${query}`);
const data = await response.json();
setResults(data.results);
} catch (error) {
console.error('Error fetching results:', error);
}
};
fetchResults();
}, [query]); // Only re-run when query changes
return (
<div>
<input
type="text"
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Search..."
/>
<ul>
{results.map(item => (
<li key={item.id}>{item.title}</li>
))}
</ul>
</div>
);
}
In this example, the useEffect hook runs whenever the query state changes. This creates a reactive search feature where the results update as the user types.
Learn how to fetch data when a component mounts using side effects.
A common use case for useEffect is fetching data when a component first mounts. This is done by using an empty dependency array, which ensures the effect only runs once after the initial render.
Here's an example of fetching data on component mount:
import React, { useState, useEffect } from 'react';
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
// Function to fetch user data
const fetchUser = async () => {
try {
setLoading(true);
const response = await fetch(`https://api.example.com/users/${userId}`);
if (!response.ok) {
throw new Error('Failed to fetch user data');
}
const userData = await response.json();
setUser(userData);
setError(null);
} catch (err) {
setError(err.message);
setUser(null);
} finally {
setLoading(false);
}
};
fetchUser();
}, [userId]); // Re-fetch when userId changes
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
if (!user) return <div>No user data found</div>;
return (
<div className="user-profile">
<h2>{user.name}</h2>
<p>Email: {user.email}</p>
<p>Username: {user.username}</p>
<p>Phone: {user.phone}</p>
</div>
);
}
This pattern is incredibly common in React applications. It handles three important states: loading, error, and success, providing a good user experience regardless of the API response.
Learn how to clean up side effects when a component unmounts.
Some side effects need to be cleaned up when a component unmounts to prevent memory leaks or unexpected behavior. Examples include canceling network requests, clearing timers, or removing event listeners.
In useEffect, you can return a cleanup function which React will call when the component unmounts or before the effect runs again (if the dependencies change).
Here's an example with a timer that gets properly cleaned up:
import React, { useState, useEffect } from 'react';
function Timer() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log('Setting up timer...');
const intervalId = setInterval(() => {
setCount(prevCount => prevCount + 1);
}, 1000);
// Cleanup function
return () => {
console.log('Cleaning up timer...');
clearInterval(intervalId);
};
}, []); // Empty dependency array means this runs once on mount
return <div>Counter: {count}</div>;
}
Another common example is removing event listeners:
import React, { useState, useEffect } from 'react';
function WindowSize() {
const [windowWidth, setWindowWidth] = useState(window.innerWidth);
useEffect(() => {
const handleResize = () => setWindowWidth(window.innerWidth);
// Add event listener
window.addEventListener('resize', handleResize);
// Cleanup: remove event listener
return () => {
window.removeEventListener('resize', handleResize);
};
}, []);
return <div>Window width: {windowWidth}px</div>;
}
Proper cleanup is essential for building robust React applications that don't leak memory or continue performing operations when they shouldn't.
Apply your knowledge by building a React application that demonstrates side effects and data fetching.