Interview question01 / 100
Practice session
React
One hundred in-depth interview questions on components, hooks, concurrent React, performance, accessibility, and production patterns.
100 curated questions10 coding exercisesQuestions tab: swipe cards or keyboard
Practice for React
Progress
1 / 100
Arrow keys to move · Space or Enter to revealOn touch: swipe the card right or left
Hands-on
Coding exercises
Read the scenario, sketch your solution in the editor, reveal hints only when you need them, then compare against our reference implementation. Exercises run from beginner through mid-level.
In this track
10
scenarios
Counter with bounds
Scenario
Build a client component `Counter` using `useState`. Show the current count, an **Increment** button (+1), a **Decrement** button (−1), and **Reset** (back to 0).
The count must never go below **0** (decrement should be a no-op at 0). Use semantic HTML (`<button type="button">`, etc.) and accessible labels if helpful.
Export `Counter` as a named export suitable for dropping into a Next.js App Router page (include `'use client'` at the top).
- TypeScript + React function component only.
- No external state libraries.
Controlled email field
Scenario
Create `EmailField` with:
• A controlled `<input type="email">` bound to React state.
• On **blur**, validate: value must match a simple pattern (contains `@` and a dot after `@`, or use a short regex).
• Show an inline error message below the input when invalid after blur; clear the error while the user is typing again (onChange) until the next blur.
Use `useState` for value and error string. Do not use a form library.
- Keep the input controlled at all times.
- Do not show an error before the first blur unless you prefer—reference solution waits until blur.
Collapsible panel
Scenario
Build `Collapsible` with props `title: React.ReactNode` and `children: React.ReactNode`. A button toggles whether `children` are visible. The button should show a clear label like "Expand" / "Collapse" (or rotate a chevron with `aria-expanded`).
Use `useState<boolean>` for open/closed. Associate the button with the region using `aria-controls` and `id` on the collapsible region.
- Must be usable with a keyboard (native button).
- Hide content with conditional render or CSS—reference uses conditional render.
Delete items from a list
Scenario
Given an initial list of todo strings (you can seed with `useState(["Buy milk", "Walk dog"])`), render each item with a **Delete** button. Clicking Delete removes only that item.
Use stable **keys** (`key={...}`)—prefer the item string if unique, or index plus prefix if you document the assumption. Do not mutate the array in place; use immutable updates (`filter`, spread, etc.).
- Functional updates are encouraged when deriving from previous state.
Fetch user on mount
Scenario
Create `UserCard` that loads a user from `https://jsonplaceholder.typicode.com/users/1` when the component mounts.
Requirements:
• While loading, show "Loading…".
• On success, show the user’s `name` and `email`.
• On failure, show a short error message.
Use `useEffect` + `fetch` (or `axios` if your app already uses it—reference uses `fetch`). Type the JSON shape minimally (`interface User { name: string; email: string }` is enough for display). Store loading/error/data in `useState`.
- Handle the async function inside useEffect; avoid async useEffect directly—use an inner async IIFE or named function.
- Cancel or ignore stale updates if the component unmounts before fetch completes (reference uses an `ignore` flag).
useLocalStorage hook
Scenario
Implement `function useLocalStorage<T>(key: string, initial: T): [T, (v: T | ((prev: T) => T)) => void]` that mirrors `useState` but persists to `localStorage`.
• On first mount, read from `localStorage`; if missing or JSON parse fails, use `initial`.
• When the setter runs, update state and write JSON.stringify to `localStorage`.
• Guard for `typeof window === "undefined"` so a first render in SSR does not throw (return `initial` until mounted if needed).
The reference uses a **mounted** flag + `useEffect` to hydrate from storage after mount to avoid SSR mismatch; you can alternatively read only in `useEffect`.
- No `use-local-storage` npm package.
- Keep serialization JSON-based for the exercise.
Debounced search value
Scenario
Implement `useDebouncedValue<T>(value: T, delayMs: number): T` that returns a **lagging** copy of `value` updated only after `delayMs` milliseconds have passed without `value` changing.
Use `useEffect` + `setTimeout` and cleanup with `clearTimeout` on change or unmount. This pattern is useful for firing API calls only after the user pauses typing.
Also sketch a tiny `SearchBox` component: controlled input + display of both immediate and debounced values for debugging (optional in your own file). The graded answer can be **only the hook** in one file.
- Delay restarts on every value change.
- Clean up timers to avoid leaks and stale updates.
Theme context
Scenario
Create a minimal dark/light theme switch using React Context.
• `ThemeProvider` wraps children and holds `"light" | "dark"` in state.
• `useTheme()` returns `{ theme, toggle }` where `toggle` flips between modes.
• Throw a clear error if `useTheme` is used outside the provider (e.g. check context === undefined).
Export `ThemeProvider`, `useTheme`, and optionally a small `ThemeToggle` button component in the same file for convenience.
- Use React.createContext with undefined default and a branded provider value.
- TypeScript: type the context value explicitly.
Todo list with useReducer
Scenario
Model a small todo app with `useReducer` (not multiple `useState` calls for the list).
Actions:
• `{ type: "add"; text: string }` — append a todo with a generated id (e.g. `crypto.randomUUID()` or incremental counter).
• `{ type: "toggle"; id: string }` — flip `done`.
• `{ type: "remove"; id: string }`.
State shape: `{ todos: { id: string; text: string; done: boolean }[] }`.
Render the list with checkboxes bound to `done`, and buttons to remove. Include a form or row to add a new todo.
- Reducer must be a pure function.
- Do not mutate previous state—return new objects/arrays.
Stable callbacks for a memoized row
Scenario
You render a long list of `ItemRow` components. Each row is wrapped in `React.memo`. The parent holds `items: { id: string; label: string }[]` and a `selectedId: string | null`.
Pass `onSelect(id: string)` from the parent. **Without** `useCallback`, the parent recreates `onSelect` every render and memo on rows is useless.
Implement the parent `ItemList` so that:
• `onSelect` is stable across renders when behavior does not change (`useCallback` with correct deps—likely `setSelectedId` from `useState` is stable, so deps can be empty or include only what’s needed).
• `ItemRow` is typed as `memo(function ItemRow(props: { id: string; label: string; selected: boolean; onSelect: (id: string) => void }))`.
Brief comment in code explaining why `useCallback` matters is a plus.
- Use React.memo and useCallback explicitly.
- Do not use external memoization libraries.