React components are pure functions of props and state — they describe what the
UI should look like, not what should happen over time. Side effects are
everything else: DOM mutations, timers, subscriptions, and network requests. The
useEffect hook is React's escape hatch for all of these.
In this challenge you will add three real side effects to the TaskFlow app:
- Dynamic document title — sync
document.titlewith the project list. - Keyboard shortcut —
Cmd/Ctrl+Kopens the "New Project" modal from anywhere on the page. - Auto-dismiss toast — a success notification that disappears after 3 seconds, with proper cleanup so no timers leak.
- Call
useEffectwith the correct dependency array to run on the right renders. - Return a cleanup function from
useEffectto avoid memory leaks and stale listeners. - Understand the three dependency array variants:
[]— run once on mount, clean up on unmount.[dep]— run whendepchanges (and on mount).- no array — run after every render (rarely what you want).
- Use
window.addEventListener/removeEventListenerinside an effect. - Use
setTimeout/clearTimeoutinside an effect. - Understand why React Strict Mode runs effects twice in development.
The start/ directory is the solution from Challenge 07. It already has:
- A working project list with filtering
- A modal-based "New Project" form
Card,Modal,PageLayout,ProjectCard,StatusFiltercomponents
What is missing:
- The document title never updates — it stays as whatever is in
index.html. - There are no keyboard shortcuts.
- Creating a project gives no feedback — the modal just closes silently.
Three // TODO comments in start/src/components/MainContent.tsx mark
exactly where to add each feature.
Run the start app to verify it works before you begin:
cd start
npm install
npm run devWork inside start/src/. The only file you need to edit is MainContent.tsx
(and optionally create Toast.tsx for the notification component).
Add a useEffect that sets document.title whenever the number of projects
changes:
- When
projects.length > 0:"Projects | TaskFlow" - When
projects.length === 0:"Get Started | TaskFlow"
Think carefully about the dependency array. This effect depends on
projects.length — nothing else.
Add a useEffect that registers a global keydown listener on window. When
the user presses Cmd+K (macOS) or Ctrl+K (Windows/Linux):
- Call
e.preventDefault()to suppress browser default behaviour. - Call
setShowForm(true)to open the modal.
The effect must return a cleanup function that calls
window.removeEventListener with the same handler reference.
What should the dependency array be? The handler only calls setShowForm —
a stable setter from useState. Consider whether it truly needs any
dependencies.
Add a success notification when a project is created:
- Add a new state variable
toastMessageof typestring | null, initialised tonull. - In
handleAddProject, after closing the modal, callsetToastMessage('Project created successfully!'). - Add a
useEffectthat watchestoastMessage. When it is notnull, start asetTimeoutthat resetstoastMessagetonullafter 3 000 ms. Return a cleanup function that callsclearTimeout. - Conditionally render a toast when
toastMessage !== null. You can create a smallToast.tsxcomponent or render the markup inline.
-
document.titleis"Projects | TaskFlow"when projects exist -
document.titleis"Get Started | TaskFlow"when no projects exist - Pressing
Cmd/Ctrl+Kopens the "New Project" modal - The keyboard listener is removed when the component unmounts (no leak)
- Adding a project shows a success toast
- The toast disappears automatically after 3 seconds
- The toast timer is cancelled if the component unmounts before 3 s
- No TypeScript errors (
npm run buildpasses)
useEffect(
() => {
// side effect here
return () => {
// optional cleanup — runs before next effect and on unmount
};
},
[dep1, dep2] // dependency array
);| Array | When does the effect run? |
|---|---|
[] |
Once on mount; cleanup on unmount |
[a, b] |
On mount and whenever a or b changes |
| (omitted) | After every render |
Without cleanup, every useEffect that adds a listener or starts a timer will
leak resources if the component unmounts or re-renders unexpectedly. React
Strict Mode deliberately unmounts and remounts every component in development to
surface these leaks early.
In development with <StrictMode>, every component mounts, immediately
unmounts (running cleanup), and mounts again. This verifies that your cleanup
function correctly reverses the effect. If you see your effects running twice,
this is expected and is not a bug in your code.
cd start
npm install
npm run devNavigate to http://localhost:5173 in your browser.
To check the TypeScript build:
npm run build