Episode 2 — React Frontend Architecture NextJS / 2.8 — useEffect Deep Dive
2.8 — Exercise Questions: useEffect Deep Dive
How to use this material:
- Try answering each question before looking at any reference material.
- For coding questions, write actual code — don't just think through it.
- Predict the output of code snippets before running them.
- If you get stuck, revisit the specific sub-topic linked in the section header.
- Build small experiments in a React project to verify your understanding.
2.8.a — What useEffect Really Does (Q1–Q12)
Q1. What is the correct mental model for useEffect — lifecycle method or synchronisation mechanism? Explain in your own words what "synchronisation" means in this context.
Q2. Name the three kinds of code in a React component and give one example of each. Which kind is useEffect?
Q3. Predict the output order:
function App() {
console.log('A: render');
useEffect(() => {
console.log('B: effect');
return () => console.log('C: cleanup');
});
return <Child />;
}
function Child() {
console.log('D: child render');
useEffect(() => {
console.log('E: child effect');
return () => console.log('F: child cleanup');
});
return <div>child</div>;
}
// On mount, what is the console output order?
Q4. A developer writes: "useEffect with [] is the same as componentDidMount." Explain why this is misleading and can cause bugs.
Q5. For each scenario, decide: useEffect or event handler?
- (a) Fetch user profile when component appears
- (b) Submit a form when user clicks "Save"
- (c) Update document.title based on current page
- (d) Send analytics when user clicks a button
- (e) Connect to a WebSocket based on current roomId
Q6. Why does useEffect run after the browser paints? What would happen if it ran before paint?
Q7. Name the three parts every well-written useEffect has. Write a template showing each part.
Q8. What is the difference between code that responds to "the user did something" vs "the component is displaying"? Give two examples of each.
Q9. True or false: useEffect runs during the render phase. Explain your answer.
Q10. Explain what happens in this code and why it might show wrong data:
function Profile({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
fetchUser(userId).then(setUser);
}, []); // Empty deps
}
// What happens when userId prop changes?
Q11. When should you use useLayoutEffect instead of useEffect? Give a specific example.
Q12. In React Strict Mode, effects run setup → cleanup → setup. Why does React do this? What bug does it catch?
2.8.b — Dependency Array Behaviour (Q13–Q24)
Q13. What are the three possible configurations for the dependency array? What does each one mean?
Q14. Explain how Object.is compares these pairs:
- (a)
42and42 - (b)
NaNandNaN - (c)
{ a: 1 }and{ a: 1 } - (d)
+0and-0
Q15. This code loops infinitely. Explain why and provide two different fixes:
function UserList() {
const [users, setUsers] = useState([]);
useEffect(() => {
fetch('/api/users').then(r => r.json()).then(setUsers);
}); // Missing dependency array
}
Q16. This code also loops infinitely. Why?
function Search({ query }) {
const [results, setResults] = useState([]);
const options = { limit: 10, sort: 'name' }; // New object each render
useEffect(() => {
fetchResults(query, options).then(setResults);
}, [query, options]);
}
Provide three different ways to fix it.
Q17. Why is setCount (from useState) safe to omit from the dependency array?
Q18. What does the exhaustive-deps ESLint rule check? Give an example of a warning it would produce.
Q19. A developer adds // eslint-disable-next-line react-hooks/exhaustive-deps to suppress a warning. In what (rare) scenarios might this be acceptable? What should they use instead most of the time?
Q20. Explain the "move function inside effect" strategy for fixing function dependencies:
// Before: function in deps
const fetchData = () => fetch(`/api/${id}`);
useEffect(() => { fetchData(); }, [fetchData]); // Loops!
// After: function inside effect
useEffect(() => {
const fetchData = () => fetch(`/api/${id}`);
fetchData();
}, [id]); // Works!
Why does moving the function inside fix the problem?
Q21. You have an object prop config = { theme: "dark", lang: "en" } in your dependency array and the effect runs every render. Suggest two strategies to fix this without changing the parent component.
Q22. Write a custom useEffectDebug hook that logs which dependencies changed when an effect re-runs.
Q23. Explain what "reference equality" means and why it matters for dependency arrays.
Q24. Your effect depends on a callback prop onUpdate from the parent. The parent doesn't wrap it in useCallback. What happens, and what are your options?
2.8.c — Cleanup Functions (Q25–Q36)
Q25. What is a cleanup function? When does it run? (Name both scenarios.)
Q26. Predict the console output:
function Chat({ roomId }) {
useEffect(() => {
console.log(`Connect: ${roomId}`);
return () => console.log(`Disconnect: ${roomId}`);
}, [roomId]);
}
// Sequence: mount with "general" → change to "random" → unmount
Q27. Why does the cleanup function see the OLD values instead of the current ones? Explain using the closure concept.
Q28. This code has a bug. Find it and fix it:
useEffect(() => {
window.addEventListener('resize', () => {
setWidth(window.innerWidth);
});
return () => {
window.removeEventListener('resize', () => {
setWidth(window.innerWidth);
});
};
}, []);
Q29. Write a cleanup function for each of these setup actions:
- (a)
const id = setInterval(tick, 1000) - (b)
const ws = new WebSocket(url) - (c)
document.addEventListener('keydown', handler) - (d)
const observer = new IntersectionObserver(callback) - (e)
const chart = new Chart(ctx, config)
Q30. What is a memory leak in the context of React effects? Give a specific example of one.
Q31. Explain the difference between AbortController cleanup and boolean flag cleanup. When would you use each?
Q32. Write a useInterval hook that properly handles cleanup. It should accept a callback and delay, and clean up correctly when the component unmounts or delay changes.
Q33. How does React Strict Mode help you find missing cleanup functions?
Q34. Your team's app gets slower the longer users use it. You suspect memory leaks from effects. Describe your debugging strategy step by step.
Q35. Does document.title = "New Title" need a cleanup function? Why or why not?
Q36. When using useEffect with multiple return paths (early returns with guards), where should the cleanup function be placed? Show an example.
2.8.d — Data Fetching Pattern (Q37–Q48)
Q37. List at least five problems with this simple fetch code:
useEffect(() => {
fetch(`/api/users/${userId}`).then(r => r.json()).then(setUser);
}, [userId]);
Q38. Explain what a race condition is in data fetching. Draw a timeline showing how one occurs.
Q39. Implement AbortController-based fetch with proper error handling. Include: loading state, error state, HTTP error checking, and abort error filtering.
Q40. What is the discriminated union pattern for fetch state? Why is it better than separate loading, error, and data state variables?
Q41. Write a useFetch hook that handles: loading/error states, AbortController for race conditions, and a refetch function.
Q42. Explain the "stale-while-revalidate" caching strategy. How would you implement it with useEffect?
Q43. Implement retry logic with exponential backoff in a useEffect-based fetch.
Q44. What is a fetch waterfall? Give an example and show how to fix it with parallel fetching.
Q45. Compare useEffect data fetching vs TanStack Query. List at least 8 features that TanStack Query handles automatically.
Q46. Write an infinite scroll hook using useEffect + IntersectionObserver.
Q47. When should you use useEffect for data fetching vs TanStack Query vs Server Components? Create a decision table.
Q48. What HTTP status codes should you handle specially in your fetch error logic? List at least 5 with appropriate user-facing messages.
2.8.e — Practical Example (Q49–Q60)
Q49. In the GitHub user search app, explain the data flow from the user typing in the search box to results appearing on screen. List every step.
Q50. Why does the app use useDebouncedValue instead of debouncing the fetch directly? What advantage does this give?
Q51. The useFetch hook uses both AbortController AND a activeUrlRef for race condition prevention. Why isn't AbortController alone sufficient?
Q52. How does the useLocalStorageState hook handle cross-tab synchronisation? What browser API does it use?
Q53. The UserCard component uses React.memo. When would this prevent a re-render? When would it NOT prevent a re-render?
Q54. Design a "prefetch on hover" feature: when the user hovers over a user card, start fetching the detail data before they click. Outline the implementation.
Q55. The app has skeleton loading states instead of a spinner. Why is this considered better UX? What information does a skeleton convey that a spinner doesn't?
Q56. If the GitHub API rate limits you (403 response), the error is shown. Design a "retry after delay" feature that automatically retries after the rate limit window (60 seconds).
Q57. The search history is stored in localStorage. What happens if localStorage is full? How would you handle this gracefully?
Q58. Add a "sort results by" dropdown (score, name, repos) to the search results. Where should this state live? How does it interact with the fetch?
Q59. The app currently fetches 30 results per page. Add pagination: "Show more" button that fetches the next page and appends results. What state changes are needed?
Q60. Write tests for the useDebouncedValue hook using React Testing Library and fake timers. Test: immediate value, debounced value after delay, and cleanup on value change.
Good luck! Build small experiments to verify your answers. The best way to learn useEffect is to break it and fix it.