Starter code bank¶
Each starter is an incomplete, self-contained exercise. Work in the interview-labs/ project. Read the lesson first, then try to complete the missing pieces before opening the Hints.
Lesson 1 — App anatomy and JSX¶
import { createRoot } from 'react-dom/client';
function App() {
// TODO: add header, main, footer, and Dashboard
return null;
}
function Dashboard() {
return <section aria-labelledby="dashboard-title">{/* TODO */}</section>;
}
const root = createRoot(document.getElementById('root')!);
root.render(<App />);
Lesson 2 — Rendering, lists, and conditional UI¶
type Topic = { id: string; title: string; completed: boolean };
function TopicList({ topics }: { topics: Topic[] }) {
const [search, setSearch] = useState('');
// TODO: derive filtered topics and render a useful empty state
const filtered = topics;
return (
<>
<input
type="search"
value={search}
onChange={(e) => setSearch(e.target.value)}
/>
<ul>
{filtered.map((topic, index) => (
// TODO: use a stable key
<li key={index}>{topic.title}</li>
))}
</ul>
</>
);
}
Lesson 3 — Props, composition, and component contracts¶
function ProgressRing(props: /* TODO: define a narrow contract */) {
// TODO: clamp the value and render an accessible progress ring
return null;
}
function Dashboard() {
return (
<div>
<ProgressRing value={105} label="Overall readiness" />
</div>
);
}
Lesson 4 — Hooks, reducer state, and derived data¶
type Topic = { id: string; title: string; completed: boolean; minutes: number };
type State = { topics: Topic[] };
type Action =
| { type: 'add'; topic: Topic }
| { type: 'toggle'; id: string };
function reducer(state: State, action: Action): State {
// TODO: implement pure, immutable transitions
return state;
}
function Tracker() {
const [state, dispatch] = useReducer(reducer, { topics: [] });
// TODO: derive completed count and total minutes during render
return null;
}
Lesson 5 — Context and dependency boundaries¶
const TrackerContext = createContext<{
state: State;
dispatch: Dispatch<Action>;
} | null>(null);
export function TrackerProvider({ children }: { children: React.ReactNode }) {
const [state, dispatch] = useReducer(reducer, { topics: [] });
// TODO: provide the value and memoize when it helps
return null;
}
export function useTracker() {
// TODO: guard against missing provider
return useContext(TrackerContext);
}
Lesson 6 — React Router and lazy routes¶
import { createHashRouter, RouterProvider } from 'react-router-dom';
const Dashboard = lazy(() => import('./pages/Dashboard'));
const TopicDetail = lazy(() => import('./pages/TopicDetail'));
const router = createHashRouter([
{
path: '/',
// TODO: add a layout and nested routes
children: [],
},
]);
function App() {
return (
<Suspense fallback={<p>Loading…</p>}>
<RouterProvider router={router} />
</Suspense>
);
}
Lesson 7 — Typed controlled forms and validation¶
type Session = { topic: string; minutes: number; notes: string };
function SessionForm({ onSubmit }: { onSubmit: (s: Session) => void }) {
const [topic, setTopic] = useState('');
const [minutes, setMinutes] = useState('');
const [notes, setNotes] = useState('');
const [error, setError] = useState<string | null>(null);
const handleSubmit = (e: FormEvent) => {
e.preventDefault();
// TODO: validate minutes between 1 and 240, then call onSubmit
};
return (
<form onSubmit={handleSubmit}>
<label htmlFor="topic">Topic</label>
<input id="topic" value={topic} onChange={(e) => setTopic(e.target.value)} />
{/* TODO: minutes and notes fields, plus error announcement */}
<button type="submit">Save session</button>
</form>
);
}
Lesson 8 — Fetching and async state¶
type Async<T> =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'success'; data: T }
| { status: 'error'; error: Error };
function useTopics() {
const [state, setState] = useState<Async<Topic[]>>({ status: 'idle' });
useEffect(() => {
const controller = new AbortController();
// TODO: fetch, check ok, validate, and handle race/stale cancellation
return () => controller.abort();
}, []);
return state;
}
Lesson 9 — Effects, cancellation, and event reasoning¶
function useTopicDetails(topicId: string) {
const [details, setDetails] = useState<Topic | null>(null);
useEffect(() => {
const controller = new AbortController();
// TODO: fetch details and guard against stale results
return () => controller.abort();
}, [/* TODO: what belongs in the dependency list? */]);
return details;
}
Lesson 10 — Vitest and React Testing Library¶
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
it('toggles a topic', async () => {
render(<TopicList topics={[{ id: '1', title: 'React', completed: false }]} />);
const checkbox = screen./* TODO: choose the right query */('checkbox');
await userEvent.click(checkbox);
expect(checkbox).toBeChecked();
});
Lesson 11 — Rendering performance and profiling¶
import { useMemo } from 'react';
function TopicSummary({ topics }: { topics: Topic[] }) {
// TODO: decide if useMemo is justified here
const expensive = topics
.filter((t) => !t.completed)
.sort((a, b) => b.priority - a.priority);
return (
<ul>
{expensive.map((t) => (
<li key={t.id}>{t.title}</li>
))}
</ul>
);
}
Lesson 12 — Architecture and production boundaries¶
Document the architecture in ARCHITECTURE.md:
# Architecture record
## Boundaries
- TODO: list route, domain state, server state, and transport boundaries.
## Rendering strategy
- SPA / SSR / streaming: TODO with tradeoffs.
## Failure modes
- TODO: list error recovery, a11y paths, and security trust boundaries.
Lesson 13 — Capstone change request¶
type ShareablePlan = {
version: number;
topicIds: string[];
targetDate: string;
weeklyMinutes: number;
};
function encodePlan(plan: ShareablePlan): string {
// TODO: serialize and encode the plan for the URL
return '';
}
function decodePlan(encoded: string): ShareablePlan {
// TODO: validate version and throw on malformed data
throw new Error('Not implemented');
}