React, as a JavaScript library for building user interfaces, provides developers with an array of tools to manage state, handle side effects, and optimize performance. Among these tools, useEffect stands out as a powerful hook for managing side effects in functional components. In this article, we’ll delve into the workings of useEffect, exploring its functionality, usage patterns, and providing diverse examples to illustrate its versatility.
What is useEffect?
useEffect is a React hook that enables developers to perform side effects in functional components. Side effects may include data fetching, subscriptions, or manually changing the DOM in ways that React components don’t traditionally do. Unlike lifecycle methods in class components, useEffect allows developers to encapsulate side effects in a way that’s concise and declarative, aligning with React’s functional paradigm.
Basic Usage
The basic syntax of useEffect is simple:
function MyComponent() {
useEffect(() => {
// Side effect code here
return () => {
// Cleanup code here
};
}, [/* dependencies */]);
return <div>My Component</div>;
}
The first argument is a function containing the side effect code.
The second argument is an optional array of dependencies. If provided, the effect will only re-run if any of the dependencies have changed since the last render.
Fetching Data
One common use case for useEffect is fetching data from an API. Let’s consider an example where we fetch a list of posts from a RESTful API using fetch:
function PostList() {
const [posts, setPosts] = useState([]);
useEffect(() => {
fetch(‘https://jsonplaceholder.typicode.com/posts’)
.then(response => response.json())
.then(data => setPosts(data));
}, []);
return (
<ul>
{posts.map(post => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
}
Subscribing to a WebSocket
Another example demonstrates subscribing to a WebSocket using useEffect. We’ll create a simple chat application that listens for messages from a WebSocket server:
function ChatApp() {
const [messages, setMessages] = useState([]);
useEffect(() => {
const socket = new WebSocket(‘ws://localhost:3000/chat’);
socket.onmessage = event => {
setMessages(prevMessages => […prevMessages, event.data]);
};
return () => {
socket.close();
};
}, []);
return (
<div>
<h1>Chat Messages</h1>
<ul>
{messages.map((message, index) => (
<li key={index}>{message}</li>
))}
</ul>
</div>
);
}
Cleaning Up Side Effects
useEffect also allows for cleanup of side effects. Consider a scenario where you set up a subscription and need to unsubscribe when the component unmounts:
function Timer() {
const [time, setTime] = useState(0);
useEffect(() => {
const intervalId = setInterval(() => {
setTime(prevTime => prevTime + 1);
}, 1000);
return () => {
clearInterval(intervalId);
};
}, []);
return <div>Time: {time} seconds</div>;
}
Updating Document Title
One interesting use case of useEffect is updating the document title dynamically based on component state. This can be useful for providing context-sensitive titles in single-page applications:
function DynamicTitle() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `Clicked ${count} times`;
}, [count]);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>Click me</button>
</div>
);
}
Geolocation Tracking
With useEffect, you can access browser APIs like geolocation. Here’s an example that tracks the user’s current position
function LocationTracker() {
const [position, setPosition] = useState(null);
useEffect(() => {
const successHandler = (position) => {
setPosition(position.coords);
};
const errorHandler = (error) => {
console.error(error);
};
navigator.geolocation.getCurrentPosition(successHandler, errorHandler);
}, []);
return (
<div>
<h2>Your Current Location:</h2>
{position ? (
<p>
Latitude: {position.latitude}, Longitude: {position.longitude}
</p>
) : (
<p>Loading…</p>
)}
</div>
);
}
Managing Local Storage
useEffect can be handy for interacting with browser storage. Here’s how you can synchronize a state variable with local storage:
function LocalStorageExample() {
const [name, setName] = useState(”);
useEffect(() => {
const storedName = localStorage.getItem(‘name’);
if (storedName) {
setName(storedName);
}
}, []);
useEffect(() => {
localStorage.setItem(‘name’, name);
}, [name]);
return (
<div>
<input
type=”text”
value={name}
onChange={(e) => setName(e.target.value)}
placeholder=”Enter your name”
/>
<p>Hello, {name}!</p>
</div>
);
}
Debouncing Input
Debouncing input is a common requirement in web development to reduce unnecessary function calls. Here’s how you can implement it using useEffect:
function DebouncedInput() {
const [input, setInput] = useState(”);
const [debouncedInput, setDebouncedInput] = useState(”);
useEffect(() => {
const timerId = setTimeout(() => {
setDebouncedInput(input);
}, 1000);
return () => {
clearTimeout(timerId);
};
}, [input]);
return (
<div>
<input
type=”text”
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder=”Enter text”
/>
<p>Debounced Input: {debouncedInput}</p>
</div>
);
}
Conclusion
These examples showcase the versatility of useEffect in managing various side effects in React applications. Whether it’s interacting with APIs, handling browser events, or synchronizing state with browser features like local storage, useEffect provides a clean and efficient way to manage side effects in functional components. By understanding its flexibility and usage patterns, developers can leverage useEffect to build robust and dynamic user interfaces in their React applications.