In this practice, we're debugging an app that uses the useEffect hook. The app has 8 errors (including console warnings) that need to be fixed for it to function properly.
Understanding the Problem
The app should work as follows:
- Start with a welcome page and Login button
- After login, show a random user from the randomuser.me API
- Allow searching for different usernames
- Maintain the user across refreshes and logouts/logins
- Return to welcome page on logout
Our task is to identify and fix 8 errors in the application.
Devising a Plan
- Examine each component's code for possible issues
- Check all useEffect hooks for correct implementation
- Look for missing dependencies in dependency arrays
- Verify localStorage is being used correctly
- Check for async function handling in useEffect
- Look for missing cleanup functions
- Check for React Router usage issues
- Verify component naming consistency
Carrying Out the Plan
Issue 1: Missing useNavigate in Unmounted.jsx
The navigate function is used but not defined in the Unmounted component.
Original code:
import { useNavigate } from 'react-router-dom';
const Unmounted = () => {
return (
<div className='unmounted'>
<h1>Welcome</h1>
<button onClick={() => navigate('/mount')}>Login</button>
</div>
);
};
Fixed code:
import { useNavigate } from 'react-router-dom';
const Unmounted = () => {
const navigate = useNavigate(); // Added this line
return (
<div className='unmounted'>
<h1>Welcome</h1>
<button onClick={() => navigate('/mount')}>Login</button>
</div>
);
};
Issue 2: Component Name Mismatch in RandomUser.jsx
The component name doesn't match the filename and export.
Original code:
const RandomUserTwo = () => {
// Component code
};
export default RandomUserTwo;
Fixed code:
const RandomUser = () => { // Changed RandomUserTwo to RandomUser
// Component code
};
export default RandomUser;
Issue 3: Async Function in useEffect without Proper Handling
The first useEffect in RandomUser.jsx has an async function but doesn't properly handle it.
Original code:
useEffect(() => {
const fetchUser = () => {
const res = await fetch(`https://randomuser.me/api/?seed=${searchWord}`);
const data = await res.json();
setData(data.results);
};
}, []);
Fixed code:
useEffect(() => {
const fetchUser = async () => { // Added async keyword
const res = await fetch(`https://randomuser.me/api/?seed=${searchWord}`);
const data = await res.json();
setData(data.results);
};
fetchUser(); // Added this line to actually call the function
}, [searchWord]); // Added searchWord to the dependency array
Issue 4: localStorage Methods Used Incorrectly
There are two issues with localStorage usage:
Issue in the useState initialization:
const [searchWord, setSearchWord] = useState(
localStorage.setItem('user') || 'foobar'
);
Fixed code:
const [searchWord, setSearchWord] = useState(
localStorage.getItem('user') || 'foobar'
);
Issue in the second useEffect:
useEffect(() => {
localStorage.getItem('user', searchWord);
}, [searchWord]);
Fixed code:
useEffect(() => {
localStorage.setItem('user', searchWord);
}, [searchWord]);
Issue 5: Missing Cleanup in setInterval useEffect
The interval is created but never cleaned up, which could lead to memory leaks.
Original code:
useEffect(() => {
const colorInterval = setInterval(() => {
console.log('i am running');
setNum((prevNum) => (prevNum === 3 ? 0 : prevNum + 1));
}, 7000);
}, []);
Fixed code:
useEffect(() => {
const colorInterval = setInterval(() => {
console.log('i am running');
setNum((prevNum) => (prevNum === 3 ? 0 : prevNum + 1));
}, 7000);
return () => {
clearInterval(colorInterval); // Added cleanup function
};
}, []);
Issue 6: Missing Key in User Mapping
The key is specified on the User component but there's another key on the render-wrapper div in User.jsx which is redundant and potentially confusing.
Original code in User.jsx:
return (
<div className='render-wrapper' key={email}>
<img src={picture.large} alt={email} />
<p>
<span className='name'>
{name.first} {name.last}
</span>
</p>
<p>{`${location.city}, ${location.state}`}</p>
<p>{email}</p>
<button onClick={() => navigate('/')}>Logout</button>
</div>
);
Fixed code:
return (
<div className='render-wrapper'> // Removed redundant key
<img src={picture.large} alt={email} />
<p>
<span className='name'>
{name.first} {name.last}
</span>
</p>
<p>{`${location.city}, ${location.state}`}</p>
<p>{email}</p>
<button onClick={() => navigate('/')}>Logout</button>
</div>
);
Issue 7: Missing Property on data Object
In the map function, we're using data?.map but when the API returns a single user, there's no guarantee that the id will have a value property. We need to use a more reliable key.
Original code:
{data?.map((data) => (
<User key={data.id.value} data={data} />
))}
Fixed code:
{data?.map((data) => (
<User key={data.email} data={data} />
))}
Issue 8: Component Not Found Error
Since we've renamed RandomUserTwo to RandomUser, we need to ensure the import in App.jsx matches.
Original import:
import RandomUser from './RandomUser';
This should be fine after fixing the component name in RandomUser.jsx.
Complete Fixed Code
Unmounted.jsx
import { useNavigate } from 'react-router-dom';
const Unmounted = () => {
const navigate = useNavigate();
return (
<div className='unmounted'>
<h1>Welcome</h1>
<button onClick={() => navigate('/mount')}>Login</button>
</div>
);
};
export default Unmounted;
RandomUser.jsx
import { useState, useEffect } from 'react';
import User from './User';
const colors = ['#0c9bbd', 'red', 'orange', 'green'];
const RandomUser = () => {
const [num, setNum] = useState(0);
const [searchChange, setSearchChange] = useState('');
const [searchWord, setSearchWord] = useState(
localStorage.getItem('user') || 'foobar'
);
const [data, setData] = useState([]);
useEffect(() => {
const fetchUser = async () => {
const res = await fetch(`https://randomuser.me/api/?seed=${searchWord}`);
const data = await res.json();
setData(data.results);
};
fetchUser();
}, [searchWord]);
useEffect(() => {
localStorage.setItem('user', searchWord);
}, [searchWord]);
useEffect(() => {
const colorInterval = setInterval(() => {
console.log('i am running');
setNum((prevNum) => (prevNum === 3 ? 0 : prevNum + 1));
}, 7000);
return () => {
clearInterval(colorInterval);
};
}, []);
return (
<div
style={{
backgroundColor: colors[num],
transition: 'background-color 4s'
}}
className='container'
>
<div className='person'>
{data?.map((data) => (
<User key={data.email} data={data} />
))}
</div>
<div className='form-wrapper'>
<form
onSubmit={(e) => {
e.preventDefault();
if (searchChange === '') return;
setSearchWord(searchChange);
setSearchChange('');
}}
>
<label htmlFor='search'>Search</label>
<input
id='search'
onChange={(e) => setSearchChange(e.target.value)}
value={searchChange}
name='searchWord'
placeholder='Username'
/>
<button type='submit'>Submit</button>
</form>
</div>
</div>
);
};
export default RandomUser;
User.jsx
import { useNavigate } from 'react-router-dom';
const User = (props) => {
const navigate = useNavigate();
const { name, email, picture, location } = props.data;
return (
<div className='render-wrapper'>
<img src={picture.large} alt={email} />
<p>
<span className='name'>
{name.first} {name.last}
</span>
</p>
<p>{`${location.city}, ${location.state}`}</p>
<p>{email}</p>
<button onClick={() => navigate('/')}>Logout</button>
</div>
);
};
export default User;
Looking Back
Summary of Fixed Issues
- Added missing
navigatedeclaration in Unmounted.jsx - Fixed component name from RandomUserTwo to RandomUser
- Properly handled async function in useEffect by adding async keyword and actually calling the function
- Fixed localStorage methods (getItem vs setItem)
- Added cleanup function for setInterval
- Removed redundant key attribute in User component
- Changed key in data mapping to use email instead of id.value
- Added searchWord to dependency array in first useEffect to fetch new data when search changes
Common useEffect Pitfalls
This exercise highlights some common useEffect issues:
- Missing dependencies: Always include all variables used inside useEffect in the dependency array, unless you specifically want to ignore changes to those variables.
- Async functions: useEffect callback cannot be async directly, but can call async functions defined inside.
- Missing cleanup: Always clean up subscriptions, timers, or event listeners to prevent memory leaks.
- Incorrect localStorage usage: getItem for retrieving data, setItem for storing data.
- Not calling fetch function: Simply defining a fetch function inside useEffect won't execute it; you need to call it.
Real-World Applications
Understanding these useEffect patterns is crucial for real-world React applications:
- Data fetching: Properly fetch data when component mounts or when dependencies change
- Subscription management: Set up and clean up subscriptions to prevent memory leaks
- Local storage sync: Keep localStorage in sync with component state
- Animation timers: Manage animation timers with proper cleanup
- Event listeners: Add and remove event listeners at the right time