Page: Technical Lesson: React Side Effects
In this lesson, we're going to focus on how to handle side effects in a React application using the useEffect hook. You'll follow along and build a small application that demonstrates some common side effects:
- Updating the document title when the user's name changes
- Fetching user data when the component mounts
- Listening for a click event and cleaning up that event listener
We'll also walk through a Git workflow, making small, descriptive commits that capture each feature as we develop it.
We have a basic React application that greets a user by name. Currently, it only displays a default user name and some static text. We want to add the following side effects to make it more dynamic and interactive:
- Document Title Effect: Whenever the user's name changes, the browser tab should display
Welcome, {userName}
. - Data Fetching: When the component first mounts, it should fetch additional user data from an external API and store it in state.
- Event Listener: We'll attach a listener to the
window
object to detect clicks, then remove this listener when the component unmounts to avoid memory leaks.
The user should be able to:
- See their name reflected in the document title when it changes.
- Retrieve user data from an external source and display it when the component mounts.
- Execute custom logic when the user clicks anywhere on the page, and stop tracking clicks when the component is removed.
- We only need one main component to handle the user's name, the fetched data, and the event listener.
- We'll use multiple
useEffect
hooks, each handling a different side effect (title update, data fetch, event subscription). - We'll create separate pieces of state for the user's name and for fetched data. We’ll also use a simple boolean or placeholder to confirm that our event listener is working.
We'll start from a GitHub repository or a local React setup. Here's the process we'll follow:
-
When X event occurs
userName
changes- Component mounts
- User clicks
-
Implement Y side effect
- Update the document title
- Fetch user data
- Log a message (or any logic you want)
-
Clean up Z effect
- For event listeners, remove them in a cleanup function to prevent memory leaks.
- Check that the document title updates when you change
userName
. - Verify that the data is fetched upon mount, and the user data appears on the screen.
- Ensure the click event is logged to the console whenever you click on the page. Remove the component (by unmounting it or navigating away) and confirm there are no further logs.
Use Git to track each step. Make small commits with clear messages. This ensures a helpful history that documents your development process.
- GitHub Repository (you can create your own or use an online editor like CodeSandbox)
- React (with
useState
anduseEffect
hooks) - Local Development Environment or an online sandbox
- Git for branching, committing, and merging your code
- Fork and clone the repository https://github.com/learn-co-curriculum/side-effects-technical-lesson.git (or create a new React app locally).
- Open your terminal and navigate into the project folder.
- Create and switch to a new branch:
git checkout -b feature-side-effects
- This separate branch will contain all your changes related to side effects.
- Open the component file where you want to manage state (App.js).
Import the necessary hooks:
import React, { useState, useEffect } from "react";```
Initialize your state for the user's name:
```jsx
const [userName, setUserName] = useState("Guest");```
Add a useEffect hook to update the document title:
```jsx
useEffect(() => {
document.title = `Welcome, ${userName}`;
}, [userName]);
This effect runs whenever userName changes.
Render an input field to change userName:
return (
<div>
<h1>Hello, {userName}!</h1>
<input
type="text"
value={userName}
onChange={(e) => setUserName(e.target.value)}
/>
</div>
);
Commit your changes:
git add .
git commit -m "Update document title using useEffect"
Create another piece of state to store fetched data:
const [userData, setUserData] = useState(null);
Add another useEffect with an empty dependency array, so it runs only once on mount:
useEffect(() => {
fetch("https://jsonplaceholder.typicode.com/users/1")
.then(response => response.json())
.then(data => {
setUserData(data);
})
.catch(error => console.error("Error fetching user:", error));
}, []);
Display the fetched data in your return statement:
return (
<div>
<h1>Hello, {userName}!</h1>
<input
type="text"
value={userName}
onChange={(e) => setUserName(e.target.value)}
/>
{userData && (
<div>
<h2>User Info</h2>
<p>Name: {userData.name}</p>
<p>Email: {userData.email}</p>
</div>
)}
</div>
);
Commit your changes:
git commit -am "Fetch user data on component mount"
Add a new useEffect for subscribing to window clicks:
useEffect(() => {
const handleWindowClick = () => {
console.log("Window was clicked!");
};
// Add event listener
window.addEventListener("click", handleWindowClick);
// Cleanup to remove the listener on unmount
return () => {
window.removeEventListener("click", handleWindowClick);
};
}, []);
This effect demonstrates how to clean up a subscription or event listener.
Commit your changes:
git commit -am "Add event listener with cleanup in useEffect"
Push your local branch to GitHub:
git push origin feature-side-effects
- Create a Pull Request:
-
Go to your repository on GitHub.
-
Click "Compare & pull request."
-
Make sure you're merging into the correct branch (e.g. main).
- Review and merge your PR when ready. After merging, you can delete the feature-side-effects branch on GitHub.
git checkout main
git pull origin main
-
Multiple useEffect Hooks: It's often clearer to use separate hooks for logically distinct side effects rather than combining everything into one.
-
Dependency Array:
-
Always list all state and props that should trigger the effect to re-run.
-
Leaving the array empty ([]) makes the effect run once on mount (and on unmount for cleanup).
-
Error Handling: Gracefully handle any failures in your fetch calls or other side effects.
-
Performance: Avoid unnecessary side effects in your code to prevent performance issues.
-
Git Best Practices: Keep commits small and well-labeled to maintain an understandable project history.
With these steps, you'll have a working example that demonstrates several types of side effects in a single React component. You can expand these examples to cover any other side effects your application needs.
Below is what your final App.js file might look like after completing all the steps and implementing each side effect:
// src/App.js
import React, { useState, useEffect } from "react";
function App() {
// 1. State for user's name
const [userName, setUserName] = useState("Guest");
// 2. State for fetched user data
const [userData, setUserData] = useState(null);
// 3. Update document title whenever userName changes
useEffect(() => {
document.title = `Welcome, ${userName}`;
}, [userName]);
// 4. Fetch data once on component mount
useEffect(() => {
fetch("https://jsonplaceholder.typicode.com/users/1")
.then(response => response.json())
.then(data => {
setUserData(data);
})
.catch(error => console.error("Error fetching user:", error));
}, []);
// 5. Set up window click listener & cleanup on unmount
useEffect(() => {
const handleWindowClick = () => {
console.log("Window was clicked!");
};
window.addEventListener("click", handleWindowClick);
return () => {
window.removeEventListener("click", handleWindowClick);
};
}, []);
return (
<div style={{ margin: "2rem" }}>
<h1>Hello, {userName}!</h1>
<input
type="text"
value={userName}
onChange={(e) => setUserName(e.target.value)}
/>
{userData && (
<div style={{ marginTop: "1rem" }}>
<h2>User Info</h2>
<p>Name: {userData.name}</p>
<p>Email: {userData.email}</p>
</div>
)}
</div>
);
}
export default App;
Use this as a reference to confirm your final implementation. Each side effect is handled in its own useEffect hook, making the code organized, easy to read, and straightforward to maintain.