Explore real-world React & Node.js/Express coding challenges with in-browser execution.
- 1. Rapid Counter — Stale State Updates - easy
The counter contains an Increment by 3 button. A single click should increase the current count by exactly three.
The current implementation calls the state setter three times, but the displayed count only increases by one. It also produces incorrect values after repeated clicks.
Debug the component so that:
- Initial count is 0.
- One click increases the count to 3.
- Two clicks result in 6.
- Updates always use the latest counter value.
- 2. Broken Product Search - easy
The page displays a list of products and provides a search box.
The product list is displayed correctly when the component first loads, but entering text into the search field doesn't update the displayed results.
Users should be able to search regardless of capitalization.
**Fix the implementation so that:**
- All products appear initially.
- Searching `"lap"` displays only Laptop.
- Search is case-insensitive.
- Clearing the input restores all products.
- A search with no matches displays the empty-state message.
- 3. Todo That Refuses to Update - easy
The Todo application renders several tasks with a Toggle button beside each task.
Clicking Toggle is supposed to switch a task between incomplete and completed. The state appears to be modified internally, but the UI doesn't reliably update.
Fix the component while preserving the existing functionality.
#Requirements:
- Three todos should render initially.
- Tasks start as incomplete.
- Clicking a task's button marks only that task complete.
- Clicking the same task again marks it incomplete.
- Other tasks must not be affected.
- 4. Resize Listener Gone Wild - easy
This component tracks how many browser resize events have occurred.
Initially it seems to work correctly. However, after the component updates, resize events begin increasing the counter more than once per event.
For example:
```
First resize -> 1
Second resize -> 3
Third resize -> unexpected...
```
A single browser event should cause exactly one state update.
Fix the component so that:
- The count starts at 0.
- One resize increases it to 1.
- Every additional resize increments it by exactly one.
- The component doesn't accumulate duplicate event listeners.
- 5. Reordering Items Mixes Up Their State - medium
The application displays three selectable developers.
Each row maintains its own selected state. The user can also reverse the order of the developers.
Selecting a developer works correctly until the list is reordered. After reversing the list, the selected state can appear on the wrong developer.
For example:
- Select `Alice`.
- Alice correctly shows `Selected`.
- Reverse the list.
- Alice should still be selected.
- Instead, another developer may now appear selected.
Fix the rendering logic so component state stays associated with the correct developer when the list order changes.
- 6. Product Ranking Dashboard - medium
An analytics dashboard displays products with their sales count and rating.
Users can sort products using:
- Top Selling — highest sales first
- Highest Rated — highest rating first
The current implementation has a bug: sorting appears to work sometimes, but switching between sorting modes produces incorrect results because the original product array is being mutated.
Fix the application so both sorting modes work correctly and repeatedly.
- 7. Shopping List Manager - easy
The Shopping List lets users enter an item and add it to their list.
There's a bug in the current application:
New items replace existing items instead of being appended.
Whitespace-only values can also be added.
Fix the component so that:
- The list starts empty.
- New items are appended.
- Existing items remain.
- The input clears after adding.
- Blank or whitespace-only items aren't added.
- 8. Course Navigator - medium
A learning platform displays one lesson at a time.
Students can use:
- Previous
- Next
- Start Over
The navigation currently contains several off-by-one bugs.
Fix it so users cannot navigate outside the available lessons.
- 9. Debug the Todo REST API - easy
You are given an existing Express Todo API that is almost complete, but several bugs are causing incorrect behavior.
Your task is to debug the existing implementation so that all Todo endpoints behave correctly and follow proper REST API conventions.
The API stores todos in memory. Each todo has the following structure:
```javascript
{
id: 1,
title: "Learn Express",
completed: false
}
```
The API must support:
- `POST /todos` — Create a new todo.
- `GET /todos` — Return all todos.
- `GET /todos/:id` — Return a specific todo.
- `PATCH /todos/:id` — Partially update an existing todo.
- `DELETE /todos/:id` — Delete an existing todo.
##Expected behavior
`POST /todos`
- `title` is required.
- An empty or whitespace-only title is invalid.
- `completed` is optional and should default to `false`.
- A successfully created todo should return 201 Created.
- Every newly created todo must receive a unique ID.
`GET /todos`
- Should return *200 OK*.
- The response body should be an array containing all todos.
`GET /todos/:id`
- Should return *200 OK* when the todo exists.
- Should return *404 Not Found* when it does not exist.
`PATCH /todos/:id`
- Should update only the fields provided in the request body.
- Existing values must not be overwritten when a field is omitted.
- If `title` is provided, it must not be empty or whitespace-only.
- Should return `404 Not Found` when the todo does not exist.
`DELETE /todos/:id`
- Should remove the todo.
- Should return *204 No Content* after successful deletion.
- Should return *404 Not Found* when the todo does not exist.
Several issues are hidden throughout the existing code. Fix the implementation without modifying the tests.
- 10. User CRUD API — Part 1: Create, Update, Delete & Email Uniqueness - medium
User CRUD API — Part 1
You're building the core of a **User Management REST API** using Express. There's no real database — user data is stored in an in-memory array that acts as a mock DB for this exercise.
The API should let clients **create**, **update**, and **delete** users, while enforcing **email uniqueness** across all users.
Endpoints to implement
| Method | Route | Description |
|--------|----------------|-----------------------------------------------|
| GET | `/` | Health check |
| POST | `/users` | Create a new user |
| GET | `/users/:id` | Fetch a single user by id |
| PUT | `/users/:id` | Update a user's name and/or email |
| DELETE | `/users/:id` | Delete a user |
Business rules
- **Create (`POST /users`)**
- `name` and `email` are required in the request body.
- If either is missing, respond with `400` and `{ "message": "Name and email are required" }`.
- If the email is **already registered** by another user, respond with `409` and `{ "message": "Email already registered" }`.
- On success, respond with `201` and the newly created user object (`id`, `name`, `email`).
- **Read (`GET /users/:id`)**
- Return the matching user with `200`.
- If no user exists with that id, respond with `404` and `{ "message": "User not found" }`.
- **Update (`PUT /users/:id`)**
- Accepts `name` and/or `email` in the body — only provided fields should be updated.
- If the user doesn't exist, respond with `404`.
- If the new email is already taken **by a different user**, respond with `409`. Updating a user with **their own current email** (i.e. no real change) must **not** be treated as a conflict.
- On success, respond with `200` and the updated user object.
- **Delete (`DELETE /users/:id`)**
- Removes the user from the store and responds with `204` (no body).
- If the user doesn't exist, respond with `404`.
Notes
- No database, no auth, no persistence — everything lives in memory for the lifetime of the process.
- Email comparisons should be treated as case-insensitive.
- 11. E-commerce Backend: Broken Checkout Flow - hard
E-commerce Backend: Broken Checkout Flow
You've inherited a small e-commerce backend built with Express. It exposes
endpoints for user authentication, browsing products, managing a shopping
cart, and placing orders — but the checkout flow has been misbehaving in
QA, and a few other rough edges have been reported.
Your job is **not** to redesign anything. The architecture, routes, and
response shapes are all intentional. Find and fix the bugs so the existing
test suite (`app.test.js`) passes.
Project structure
- `index.js` — starts the server (do not modify)
- `constants.js` — shared config values (JWT secret, etc.)
- `db.js` — in-memory data store (users, products, carts, orders)
- `middleware/auth.js` — JWT-based authentication middleware
- `app.js` — the Express app: routes for auth, products, cart, and orders
- `app.test.js` — the test suite your solution must satisfy (do not modify)
Data model
**Product**
```json
{ "id": 1, "name": "Wireless Mouse", "price": 25.99, "stock": 10 }
```
**Cart item** (stored per user)
```json
{ "productId": 1, "quantity": 2 }
```
**Order**
```json
{
"id": 1,
"userId": 1,
"items": [{ "productId": 2, "name": "Mechanical Keyboard", "quantity": 2, "price": 79.99 }],
"total": 159.98,
"createdAt": "2026-08-21T10:00:00.000Z"
}
```
Endpoints
Auth
- `POST /auth/register` — `{ username, password }` → `201` with `{ id, username }`. `400` if fields are missing, `409` if the username is taken.
- `POST /auth/login` — `{ username, password }` → `200` with `{ token }`. `401` on bad credentials.
Products
- `GET /products` → `200` with `{ products }`.
- `GET /products/:id` → `200` with the product, or `404` if it doesn't exist.
Cart (requires `Authorization: Bearer <token>`)
- `GET /cart` → `200` with `{ cart }` for the authenticated user only.
- `POST /cart` — `{ productId, quantity }` → `201` with the updated cart. Adding a product already in the cart should **increase** its quantity, not overwrite it. `404` if the product doesn't exist.
- `DELETE /cart/:productId` → `200` with the updated cart, or `404` if the item isn't in the cart.
Orders (requires `Authorization: Bearer <token>`)
- `POST /orders` — creates an order from the authenticated user's current cart, decrements product stock, and clears the cart. `400` if the cart is empty. **An order must never be created if it would take any product's stock below zero.**
- `GET /orders` → `200` with `{ orders }` for the authenticated user only.
Notes
- Carts and orders are scoped per user — one user should never see or affect another user's cart.
- Passwords must never be returned in API responses, and must be verified securely, not compared as plain strings.
- Order totals must reflect quantity, not just unit price.
- Everything you need is already installed in the environment (`express`, `bcrypt`, `jsonwebtoken`, `jest`, `supertest`). No `package.json` changes needed.
- 12. HTTP Key-Value Store - medium
Build a simple **in-memory key-value store** and expose it through an Express HTTP API.
The goal of this challenge is to practice:
- Creating and using JavaScript classes
- Designing REST-style HTTP endpoints
- Working with Express route parameters
- Handling JSON request bodies
- Returning appropriate HTTP status codes
- Connecting a simple frontend interface to an API
---
Requirements
1. KeyValueStore Class
Create a `KeyValueStore` class inside `store.js`.
Each instance should maintain its own in-memory collection of key-value pairs.
Implement the following methods:
`set(key, value)`
Stores a value under the provided key.
If the key already exists, its value should be replaced.
```js
store.set("name", "Alice");
````
---
`get(key)`
Returns the value associated with the key.
If the key does not exist, return `undefined`.
```js
store.get("name");
```
---
`delete(key)`
Deletes the key from the store.
Return:
* `true` if the key existed and was deleted
* `false` if the key did not exist
---
`has(key)`
Returns `true` if the key exists and `false` otherwise.
---
`getAll()`
Returns an object containing every key-value pair currently stored.
Example:
```json
{
"name": "Alice",
"language": "JavaScript"
}
```
---
HTTP API
Create an Express application in `app.js`.
The server must support the following endpoints.
Set a value
`PUT /store/:key`
Request body:
```json
{
"value": "Alice"
}
```
Successful response:
**Status:** `200`
```json
{
"key": "name",
"value": "Alice"
}
```
The `value` property is required.
If it is missing, return:
**Status:** `400`
```json
{
"error": "Value is required"
}
```
---
Get a value
`GET /store/:key`
If the key exists:
**Status:** `200`
```json
{
"key": "name",
"value": "Alice"
}
```
If the key does not exist:
**Status:** `404`
```json
{
"error": "Key not found"
}
```
---
Delete a value
`DELETE /store/:key`
If the key exists and is deleted:
**Status:** `200`
```json
{
"message": "Key deleted"
}
```
If the key does not exist:
**Status:** `404`
```json
{
"error": "Key not found"
}
```
---
Get all values
`GET /store`
Return all currently stored key-value pairs.
**Status:** `200`
Example:
```json
{
"name": "Alice",
"language": "JavaScript"
}
```
---
Web Interface
A frontend interface is provided in `index.html`.
Your Express application must serve this file when a user visits:
`GET /`
The page interacts with the API that you build.
---
Important Notes
* Store all data **in memory**.
* Do not use a database.
* Your key-value store must be implemented using a **class**.
* Export the `KeyValueStore` class from `store.js`.
* Export the Express application from `app.js`.
* Do not call `app.listen()` inside `app.js`.
* `index.js` already starts the server for you.
* Data does not need to survive a server restart.
- 13. Persistent Tab Form - medium
Persistent Tab Form
Build a reusable React tabbed form where users can switch between multiple tabs without losing the information they have already entered.
The form contains multiple tabs representing different sections of a profile:
- **Personal** — Name and email
- **Address** — City and postal code
- **Preferences** — Job title and experience
Users should be able to freely switch between tabs while filling out the form. Any information entered into one tab must remain intact when the user navigates to another tab and then returns.
Current Problems
The existing implementation has several bugs related to React state management and controlled form inputs.
Your task is to debug the implementation so that:
1. Switching between tabs does not reset the fields in the previous tab.
2. Text entered into each field remains available after switching tabs.
3. Each input correctly reflects its current value.
4. Updating a field does not accidentally overwrite values from other fields.
5. Switching tabs only changes the visible section and does not reset the form state.
6. The active tab is correctly highlighted.
7. The correct tab content is displayed after switching.
8. All form fields remain controlled React inputs.
9. Empty fields are preserved when navigating between tabs.
Expected Behavior
For example:
1. Open the **Personal** tab.
2. Enter `John` into the name field.
3. Enter `john@example.com` into the email field.
4. Switch to **Address**.
5. Enter `Hyderabad` as the city.
6. Switch back to **Personal**.
7. `John` and `john@example.com` should still be present.
8. Switch back to **Address**.
9. `Hyderabad` should still be present.
The same behavior should work for every tab and every form field.
Requirements
- Use React state to maintain the form data.
- Keep the form state at a level that survives tab switching.
- Do not rely on the DOM to preserve input values.
- Do not use page reloads or browser storage to solve the problem.
- Switching tabs must not reset previously entered values.
- The component should remain reusable and maintainable.
Files to Modify
Modify the provided React component implementation to fix the broken behavior.
Do **not** modify the test files.
Acceptance Criteria
The solution is correct when:
- All tabs can be switched between freely.
- No previously entered value is lost.
- Each field maintains its own value.
- Editing one field does not clear or overwrite unrelated fields.
- The active tab always displays the correct content.
- All provided tests pass.
- 14. React Pagination - medium
**You are building an admin dashboard view that displays a large set of mock inventory items. The current implementation fetches/loads all 32 items at once and renders them in a raw list, leading to poor visual organization.**
Your task is to implement full client-side pagination inside `src/PaginatedList.jsx`.
**Current Problems & Requirements:**
1. **Page Sizing & Slicing**:
* Calculate total pages using `Math.ceil(items.length / pageSize)`.
* Display only the items belonging to the current page.
* Items are 1-indexed for display. Page `1` with `pageSize = 5` should show items from index `0` to `4`.
2. **Page Controls**:
* Render **Previous** and **Next** buttons.
* Disable the **Previous** button when on the first page (`page === 1`).
* Disable the **Next** button when on the last page (`page === totalPages`).
* Render numbered page buttons from `1` to `totalPages`. The active page button must have the class `active`.
3. **Page Size Selector**:
* Render a `<select>` dropdown with options `[5, 10, 20]`.
* Changing the page size should reset the current page back to `1`.
4. **State Boundaries & Edge Cases**:
* Display a message reading `"No items found"` when the `items` prop array is empty.
* Handle page navigation bounds cleanly so users cannot navigate to invalid page numbers.
**Files to Edit:**
* `src/PaginatedList.jsx`
**Acceptance Criteria:**
* Only the sliced items for the current page are rendered inside `ul.item-list`.
* Changing pages via Previous/Next or numbered buttons updates the items correctly.
* Adjusting the page size dropdown resets current page state back to `1` and updates items shown.
* Buttons reflect correct `disabled` attributes at lower and upper page boundaries.
- 15. Implement an In-Memory Redis Server with Expiration & LRU Eviction - hard
Mini Redis API (LRU Cache & TTL Engine)
You are tasked with building a lightweight, in-memory **Mini Redis API** using Express. The application should act as a key-value store supporting string operations, Time-To-Live (TTL) expiration, and Least Recently Used (LRU) capacity eviction.
Currently, the server routing, storage engine, and command handlers are non-functional or incomplete. None of the test cases are passing.
Requirements
Implement the API endpoints in `src/cacheEngine.js` and `src/app.js` to satisfy the following requirements:
1. Basic Key-Value Operations
- **`POST /api/set`**: Accepts `{ "key": string, "value": string, "ttl": number (optional) }`.
- Stores the key-value pair.
- If optional `ttl` (in seconds) is provided, sets an expiration timer for that key.
- Returns status `200` with `{ "success": true, "key": string }`.
- Returns status `400` if `key` or `value` is missing/invalid.
- **`GET /api/get/:key`**:
- If the key exists and is **not expired**, returns status `200` with `{ "key": string, "value": string }`.
- Reading a key marks it as **recently used** (updates LRU order).
- If the key does not exist or has expired, returns status `404` with `{ "error": "Key not found" }`.
- **`DELETE /api/del/:key`**:
- Removes the key from the cache.
- Returns status `200` with `{ "success": true, "deleted": true }` if key existed, or `{ "success": false, "deleted": false }` if key was not found.
2. TTL & Expiration Handling
- **`POST /api/expire`**: Accepts `{ "key": string, "seconds": number }`.
- Sets/updates expiration timeout on an existing key.
- Returns status `200` with `{ "success": true }` if key exists.
- Returns status `404` with `{ "error": "Key not found" }` if key does not exist or is already expired.
- **`GET /api/ttl/:key`**:
- Returns remaining TTL in seconds: status `200` with `{ "key": string, "ttl": number }`.
- If key exists but has no expiration, return `{ "key": string, "ttl": -1 }`.
- If key does not exist or is expired, return status `404` with `{ "error": "Key not found" }`.
3. LRU Eviction Policy
- The cache has a fixed capacity limit configured via environment variable `CACHE_CAPACITY` (default to **3** if not set or invalid).
- When a new key is inserted via `/api/set` and capacity is reached, the **Least Recently Used (LRU)** non-expired key must be evicted.
- Updating an existing key's value via `/api/set` updates its value/TTL and marks it as recently used, but does **not** increase total count or trigger eviction.
- Any access (`GET /api/get/:key`) marks the key as recently used.
---
Instructions
1. Edit **`src/cacheEngine.js`** to build the underlying `LRUCache` data structure and expiration logic.
2. Edit **`src/app.js`** to hook up the HTTP routes to your cache engine.
3. Ensure all tests in `tests/redis.test.js` pass.
Files to Edit
- `src/cacheEngine.js`
- `src/app.js`
- 16. Debounced Product Search Componentc - medium
Debounced Product Search
You are working on an e-commerce dashboard that features an interactive product catalog search. To prevent excessive rendering and avoid spamming simulated API requests on every single keystroke, the search field must be debounced.
The Bug / Unimplemented Behavior
The current implementation in `src/ProductSearch.jsx` updates the search query instantly on every keystroke, causing rapid UI recalibrations and immediate filtering. Furthermore, the search bar fails to clean up active timers when unmounted or when fast consecutive inputs occur.
Requirements
1. **Implement Debounced Search State**:
* Create a 300ms debounce delay for the search input.
* As the user types into the text input (`data-testid="search-input"`), update the `searchTerm` immediately in the input field to maintain responsive typing UI.
* Update the filtered products list **only after 300ms of inactivity** since the last keystroke.
2. **Timer Cleanup**:
* Ensure any active debounce timer is properly cleaned up on component unmount or when the search input changes before the 300ms timer expires.
3. **Product Filtering Logic**:
* Filter the products imported from `src/products.js` by matching the debounced search term against product names or categories (case-insensitive search).
* Render filtered products in the list container (`data-testid="product-list"`). Each product item should display its `name`, `category`, and `price`.
* If no products match the debounced term, render a "No products found" message (`data-testid="no-products"`).
4. **Status Indicator**:
* While waiting for the 300ms debounce delay after a user types, display a searching indicator with text `"Searching..."` (`data-testid="search-status"`).
* Once the debounced term updates, hide the status indicator.
Files to Modify
* `src/ProductSearch.jsx`
- 17. Express API Pagination with Limit and Skip - medium
Objective
Implement a backend pagination endpoint for a product catalog API using query parameters `limit` and `page` (or `skip`).
Background
The product catalog contains a mock dataset loaded in `mockProducts.js`. Currently, GET `/api/products` returns the entire dataset without any pagination or metadata validation, which causes high payload sizes and poor performance as the dataset grows.
Requirements
Modify `app.js` to update the `GET /api/products` endpoint to handle pagination using query parameters:
1. **Query Parameters**:
* `limit` (optional): The maximum number of products to return per page.
* Default value: `10`
* Must be a positive integer ($> 0$).
* Maximum allowed limit: `50`. If a limit greater than `50` is provided, clamp/cap it to `50`.
* `page` (optional): The page number requested.
* Default value: `1`
* Must be a positive integer ($> 0$).
* Calculate zero-indexed skip/offset as: $\text{skip} = (\text{page} - 1) \times \text{limit}$.
* Alternatively, allow an explicit `skip` query parameter:
* If `skip` is provided directly in the query, use it over the computed `page` offset. `skip` must be a non-negative integer ($\ge 0$, default `0`).
2. **Validation & Error Handling**:
* If `limit`, `page`, or `skip` parameters are provided but are invalid (e.g., non-numeric strings like `"abc"`, negative numbers, or `page`/`limit` equal to `0`), return HTTP status code `400 Bad Request` with an error message:
```json
{ "error": "Invalid pagination parameters" }
```
3. **Response Payload Structure**:
The API must respond with HTTP status `200` and a structured JSON object containing:
```json
{
"data": [ ...array of products for current page... ],
"pagination": {
"totalItems": 105,
"totalPages": 11,
"currentPage": 1,
"limit": 10,
"hasNextPage": true,
"hasPrevPage": false
}
}
```
Files to Edit
* `app.js` — Implement pagination logic and parameter validations inside the `GET /api/products` handler.
- 18. Debugging Modal Stack Keyboard Event Listeners - medium
Debugging Modal Stack Keyboard Event Listeners
You are working on a dashboard application that features three distinct modals:
1. **Login Modal**
2. **Settings Modal**
3. **Confirmation Modal**
The business requirement states that **only one modal can be active/open at any given time**. Furthermore, pressing the **Escape key** should instantly close whichever modal is currently open.
The Bug
The current implementation in `src/ModalManager.jsx` is broken and exhibits severe event listener bugs:
* Pressing `Escape` often fails to close the active modal, closes the wrong modal, or causes erratic state updates.
* Event listeners are being registered repeatedly without proper cleanup, accumulating duplicate handlers on every render.
* Handlers capture stale state values due to incorrect dependency management in `useEffect`.
* Unmounting components leaves dangling `keydown` event listeners on the global `window` object.
Requirements & Acceptance Criteria
1. **Single Active Modal**: Ensure state transitions strictly enforce that only one modal (`"login"`, `"settings"`, or `"confirm"`) is active at a time, or `null` when none are open.
2. **Global Escape Key Handler**: Register a single global `keydown` event listener for the `Escape` key that reliably closes whichever modal is currently active.
3. **Stale Closure Prevention**: Ensure the keyboard event listener always reads the up-to-date active modal state.
4. **Proper Listener Cleanup**: Ensure event listeners are properly removed when components re-render or unmount to avoid memory leaks and duplicate triggers.
Files to Modify
* `src/ModalManager.jsx` — Fix the state management and `useEffect` lifecycle/event listener registration logic.
- 19. Debug Express Middleware Order, Flow, and Error Handling - medium
Express Middleware Pipeline Debugging
You are working on an Express API service that manages user profiles, admin operations, and order creation. The production application has several critical bugs where requests hang infinitely, incorrect status codes are returned, and server errors leak sensitive stack traces to users.
API Endpoints
- `GET /api/profile` - Protected route (Requires valid Bearer token)
- `GET /api/admin/users` - Admin-only route (Requires valid Bearer token + admin role)
- `POST /api/orders` - Protected route with body validation (`item` string, `quantity` positive integer)
Current Issues & Bug Reports
1. **Hanging Requests:** Certain valid endpoints never return a response to the client.
2. **Double Headers Sent Errors:** Uncaught exceptions occur on bad requests in certain routes.
3. **Incorrect Authorization Codes:** Unauthorized or forbidden requests respond with inappropriate HTTP status codes (e.g., 401 vs 403 confusion).
4. **Error Middleware Breakdown:** Validation errors and standard server errors bypass the global error handler or do not receive the expected status code structure.
5. **Route-Level Order Issues:** Protected endpoints allow unauthenticated access or invoke route handlers before running required middleware.
Tasks
Inspect `src/middleware.js` and `src/app.js` to identify and fix all middleware execution flow issues:
- Ensure all custom middleware functions correctly pass control using `next()` or send responses without double-invoking handlers.
- Verify authentication and authorization middleware enforce strict status codes:
- `401 Unauthorized` for missing or invalid tokens.
- `403 Forbidden` for valid tokens without administrative privileges.
- Configure validation middleware to forward errors cleanly to the centralized error handler using `next(err)`.
- Ensure the centralized error-handling middleware uses the correct signature `(err, req, res, next)` and handles both operational validation errors and unexpected internal server errors.
- Assemble the middleware chain in `src/app.js` in the correct execution sequence.
Acceptance Criteria
- `GET /api/profile` returns `200 OK` for authenticated users and `401 Unauthorized` without a valid token.
- `GET /api/admin/users` returns `403 Forbidden` when non-admin users attempt access.
- `POST /api/orders` returns `400 Bad Request` with an appropriate error payload when fields are missing or invalid.
- Centralized error handler formats errors as `{ error: message }` with appropriate status codes (`400` for validation, `500` for general errors).
- 20. Implement Idempotent Checkout & Prevent Duplicate Orders - hard
Implement Idempotent Checkout & Prevent Duplicate Orders
In e-commerce applications, network instability or accidental user double-clicks can cause duplicate order submissions (`POST /api/orders`). Creating duplicate orders results in overcharging customers and inventory inconsistencies.
Your task is to implement **Idempotency Key support** on the Express backend and **double-submit prevention** on the React frontend.
---
Context & Problem
Currently, every request to `POST /api/orders` creates a brand-new order record in the database regardless of whether it is a duplicate attempt. Additionally, the checkout form in the frontend does not disable the submit button during an active submission.
Requirements
1. Backend (`server/orders.js`)
* Read the `Idempotency-Key` header from incoming requests to `POST /api/orders`.
* If `Idempotency-Key` is **missing**, return HTTP `400 Bad Request` with `{ error: "Idempotency-Key header is required" }`.
* If payload validation fails (e.g., missing `items` array or `items` is empty), return HTTP `400 Bad Request` with `{ error: "Invalid order payload" }`.
* If an order with the given `Idempotency-Key` **already exists**:
* Do **not** create a new order or deduct inventory again.
* Return the **previously created order** with HTTP `200 OK` and set the response header `X-Cache-Hit: true`.
* If it is a **new valid request**:
* Store the order mapped to the `Idempotency-Key`.
* Return the newly created order with HTTP `201 Created` and set the response header `X-Cache-Hit: false`.
2. Frontend (`src/Checkout.jsx`)
* Prevent duplicate submissions from the UI while a checkout request is pending:
* Disable the "Place Order" button during submission.
* Prevent secondary submit events while an active request is in flight.
* Generate a unique idempotency key per checkout attempt and send it via the `Idempotency-Key` header in the `fetch` request.
---
Files to Modify
* `server/orders.js`
* `src/Checkout.jsx`
- 21. Add Todos - easy
Step 1: Add Todos
The skeleton is in place. Wire up the input and button so the user can add todos to the list.
Requirements
1. Track the input value in state
2. Clicking **Add** appends the typed text as a new `<li>` in `<ul id="todos">`
3. After adding, **clear the input**
4. Ignore empty/whitespace-only submissions
5. Each `<li>` should have a stable `key` (use `Date.now()` or a counter — anything but the array index)
- 22. Component Composition - medium
Component Composition
React favors **composition over inheritance**. The `children` prop lets you nest content inside components.
The children Pattern
```jsx
function Card({ children, title }) {
return (
<div className="card">
<h3>{title}</h3>
<div className="card-body">{children}</div>
</div>
);
}
// Usage
<Card title="Profile">
<p>This is the card content!</p>
</Card>
```
Your Task
1. Create a `Card` component that:
- Accepts `title` (string) and `children` props
- Renders a wrapper `<div className="card">`
- Shows the title in `<div className="card-header">`
- Renders children inside `<div className="card-body">`
2. Create an `Alert` component that:
- Accepts `type` prop ("info", "success", "warning") and `children`
- Renders `<div className="alert alert-{type}">`
- Renders children inside
3. In `App`, compose them:
- A `Card` with title **"Notifications"** containing:
- An `Alert` of type **"success"** with text **"Task completed!"**
- An `Alert` of type **"warning"** with text **"Low disk space"**
- 23. Components & Props - easy
Components & Props
Components are the building blocks of React. A component is just a function that returns JSX. **Props** let you pass data from a parent component to a child.
Key Concepts
- Components must start with an **uppercase letter**
- Props are passed like HTML attributes: `<Greeting name="Alice" />`
- Inside the component, access them via the function parameter: `function Greeting({ name })`
- Props are **read-only** — never modify them
Your Task
1. Create a `UserCard` component that accepts `name` and `role` props
- Render an `<h2>` with the name
- Render a `<p>` with the role
- Wrap both in a `<div>` with `className="user-card"`
2. In the `App` component, render **two** `UserCard` components:
- One with name **"Alice"** and role **"Developer"**
- One with name **"Bob"** and role **"Designer"**
- 24. Conditional Rendering - easy
Conditional Rendering
React doesn't have special directives like `v-if`. Instead, you use plain JavaScript:
Patterns
```jsx
// Ternary
{isLoggedIn ? <Dashboard /> : <LoginForm />}
// && short-circuit (render or nothing)
{showBanner && <Banner />}
// Early return
if (isLoading) return <Spinner />;
```
Your Task
Build a login/logout toggle:
1. Track `isLoggedIn` state, starting as **false**
2. When logged out:
- Show `<p id="status">` with text **"Please log in"**
- Show a button with id `"login-btn"` and text **"Log In"**
3. When logged in:
- Show `<p id="status">` with text **"Welcome back!"**
- Show a button with id `"logout-btn"` and text **"Log Out"**
4. Add a `<span id="badge">` that only appears when logged in, containing **"Online"**
- 25. Context API - medium
Context API
When data needs to be accessed by many components at different nesting levels, **Context** lets you pass it without threading props through every level.
How It Works
```jsx
// 1. Create context
const ThemeContext = createContext('light');
// 2. Provide value at top level
<ThemeContext.Provider value="dark">
<App />
</ThemeContext.Provider>
// 3. Consume anywhere below
function Button() {
const theme = useContext(ThemeContext);
return <button className={theme}>Click</button>;
}
```
Your Task
Build a theme switcher using Context:
1. Create a `ThemeContext` using `createContext`
2. In `App`, track `theme` state (`"light"` or `"dark"`), starting with **"light"**
3. Wrap children in `ThemeContext.Provider` with value `{ theme, toggleTheme }`
4. Create a `ThemeDisplay` component that:
- Uses `useContext(ThemeContext)` to read the theme
- Renders `<div id="theme-display">` with text **"Current theme: light"** or **"dark"**
- Renders a button with id `"toggle-btn"` that calls `toggleTheme`
5. `ThemeDisplay` must NOT receive theme as a prop — it must use Context
> The key idea: ThemeDisplay reads the theme from Context, not from props.
- 26. Custom Hooks - medium
Custom Hooks
A **custom hook** is a function that starts with `use` and can call other hooks. It lets you extract reusable stateful logic.
Example
```jsx
function useToggle(initial = false) {
const [value, setValue] = useState(initial);
const toggle = () => setValue(v => !v);
return [value, toggle];
}
// Usage
function App() {
const [isOn, toggle] = useToggle(false);
return <button onClick={toggle}>{isOn ? 'ON' : 'OFF'}</button>;
}
```
Your Task
1. Create a custom hook called `useCounter` that:
- Accepts an `initialValue` parameter (default: 0)
- Returns an object: `{ count, increment, decrement, reset }`
- `increment` adds 1, `decrement` subtracts 1, `reset` goes back to initial
2. Use `useCounter` in TWO separate components:
- `CounterA`: uses `useCounter(0)`, renders in a div with id `"counter-a"`
- `CounterB`: uses `useCounter(10)`, renders in a div with id `"counter-b"`
- Each should display the count in a `<span className="count">` and have increment/decrement buttons
3. **Crucially**, each counter should work **independently** — clicking one doesn't affect the other.
- 27. Error Boundaries - medium
Error Boundaries
A JavaScript error in a child component shouldn't take down your whole app. **Error boundaries** are special components that catch errors during rendering and show a fallback UI instead.
There's no hook equivalent yet — boundaries must be **class components** that implement at least one of:
- `static getDerivedStateFromError(error)` — returns next state when a child throws
- `componentDidCatch(error, info)` — for logging
```jsx
class Boundary extends React.Component {
state = { hasError: false };
static getDerivedStateFromError() { return { hasError: true }; }
render() {
if (this.state.hasError) return <p>Something went wrong</p>;
return this.props.children;
}
}
```
Your Task
Build an error boundary that protects a buggy button:
1. Define class `ErrorBoundary` with `hasError` state and `getDerivedStateFromError`
2. When caught, render `<p id="error-fallback">Something went wrong</p>`
3. Otherwise, render `this.props.children`
4. Wrap the provided `<BuggyButton />` in `<ErrorBoundary>`
- 28. Event Handling - easy
Event Handling
React events are named using camelCase: `onClick`, `onChange`, `onSubmit`.
You pass a **function** as the handler, not a string.
Key Patterns
```jsx
// Inline handler
<button onClick={() => alert('clicked!')}>Click</button>
// Named handler
function handleClick() { alert('clicked!'); }
<button onClick={handleClick}>Click</button>
// With event object
<input onChange={(e) => setValue(e.target.value)} />
```
Your Task
Build a simple item list:
1. Create an input with id **"item-input"** for typing items
2. Track the input value in state
3. Add a button with id **"add-btn"** that adds the input text to a list
4. After adding, **clear the input**
5. Render the list as `<ul id="item-list">` with `<li>` elements
6. Each `<li>` should display the item text
- 29. Forms & Controlled Components - easy
Forms & Controlled Components
In React, form elements like `<input>`, `<textarea>`, and `<select>` can be **controlled** — their value is driven by React state.
Pattern
```jsx
const [name, setName] = useState('');
<input value={name} onChange={(e) => setName(e.target.value)} />
```
Your Task
Build a registration form:
1. A text input for **name** with id `"name-input"`
2. An **email** input with id `"email-input"`
3. A `<select>` for **role** with id `"role-select"` and options: `"student"`, `"developer"`, `"designer"`
4. A **submit button** with id `"submit-btn"`
5. All inputs must be **controlled** (value + onChange)
6. On submit, **prevent page reload** and show a `<div id="summary">` displaying: **"Name: [name], Email: [email], Role: [role]"**
7. The summary should only appear after submission
- 30. The Conditional Hook - easy
Fix the Bug: The Conditional Hook
This profile component has a "Show Details" toggle. When details are shown, additional info is displayed. But toggling it crashes the entire app with a hooks error.
Symptoms
- Initial render works fine
- Clicking "Show Details" crashes the app
- Error: "Rendered more hooks than during the previous render"
Your Task
Fix the code so that:
- Toggling "Show Details" works without crashing
- When checked, shows the detail fields
- When unchecked, hides them
- All hooks must follow the Rules of Hooks
> **Think about it:** What is the #1 rule of React Hooks?
- 31. The Page Reload - medium
Fix the Bug: The Page Reload
The form has a submit handler that should display a greeting. Instead, hitting Submit causes the page to reload (you’ll see the iframe flicker), state resets, and the greeting never appears.
Symptoms
- Click Submit — page reloads
- The `#result` greeting never sticks
- Inputs lose their value
Your Task
Make Submit display the greeting **without** reloading the page.
> **Think about it:** HTML forms have a default browser behavior — they submit to the server and navigate away. React doesn’t override that for you. Which method on the event object cancels the default action?
- 32. The Frozen Counter - easy
Fix the Bug: The Frozen Counter
The counter component below renders correctly at first — you see "Count: 0" and two buttons. But when you click **Increment** or **Decrement**, nothing happens. The count stays frozen at 0.
Symptoms
- Initial render looks correct
- Clicking buttons has no visible effect
- No errors in the console
Your Task
Find and fix the bug so that:
- Clicking Increment increases the count
- Clicking Decrement decreases the count
> **Think about it:** How does React know to re-render a component?
- 33. The Infinite Loop - easy
Fix the Bug: The Infinite Loop
This component is supposed to fetch a greeting message and display it. But as soon as it mounts, the browser freezes with a **"Too many re-renders"** error.
Symptoms
- Browser tab becomes unresponsive
- Console shows: "Too many re-renders. React limits the number of renders to prevent an infinite loop."
- Component never displays properly
Your Task
Fix the code so that:
- The message is set **only once** when the component mounts
- No infinite re-render loop occurs
- The greeting displays correctly
> **Think about it:** What happens when you call setState during every render?
- 34. The Instant Fire - easy
Fix the Bug: The Instant Fire
This component has a button that should show an alert with the count when clicked. But the alert fires **immediately** when the component renders, and clicking the button does nothing.
Symptoms
- Alert appears on page load (before any click)
- Clicking the button has no effect
- The count display works fine otherwise
Your Task
Fix the event handler so that:
- No alert on initial render
- Clicking the button shows the alert with the current count
> **Think about it:** What's the difference between `onClick={fn()}` and `onClick={fn}`?
- 35. The Wandering Edit - medium
Fix the Bug: The Wandering Edit
The list renders fine. The reverse button reorders the array. But edits to the input fields don't follow their item — they stay glued to the **position** they were typed in.
Symptoms
- Edit the Apple input to "Apple Pie"
- Click **Reverse**
- The text "Apple Pie" now appears on **Cherry** (the new top row), not on the Apple input
Your Task
Make each input stay with its item after a reorder.
> **Think about it:** React decides whether to reuse or recreate a child by its **`key`**. What happens when the key is the array index? When items move, the indices don't — so position 0 keeps the same DOM node even though it now holds a different item.
- 36. The Missing Cleanup - easy
Fix the Bug: The Missing Cleanup
This component has a timer that increments a seconds counter every second. There's also a separate click counter. But something is wrong — every time you click the Increment button, the timer seems to speed up. After a few clicks, seconds are flying by.
Symptoms
- Timer starts correctly at 1 tick/second
- After clicking Increment a few times, the timer accelerates
- Eventually the seconds counter is updating multiple times per second
Your Task
Fix the useEffect so that:
- The timer always ticks at exactly 1 second intervals
- Clicking Increment doesn't affect the timer speed
- No intervals are left running when they shouldn't be
> **Think about it:** What happens to the old interval when useEffect runs again?
- 37. The Disappearing List - easy
Fix the Bug: The Disappearing List
This todo list lets you add and delete items. But when you delete an item from the middle of the list, strange things happen — wrong items disappear, or the list gets jumbled.
Symptoms
- Adding items works fine
- Deleting the last item works
- Deleting from the middle causes wrong items to be removed
- React may show a warning about keys in the console
Your Task
Fix the list rendering so that:
- Each item can be correctly deleted
- The list updates predictably
- No React key warnings
> **Think about it:** How does React identify which item is which in a list?
- 38. The Mutant Array - easy
Fix the Bug: The Mutant Array
This component lets you add fruit items to a list. But when you click "Add Item", nothing happens on screen. The list stays frozen, even though the code looks like it's adding items.
Symptoms
- Clicking "Add Item" has no visible effect
- The list stays at its initial state
- No console errors
Your Task
Fix the `addItem` function so that new items actually appear in the list.
> **Think about it:** Does React detect the change if you mutate the existing array?
- 39. The Dropped Prop - medium
Fix the Bug: The Dropped Prop
`App` owns the theme. `Label` displays it. In between sit `Section` and `Card` — pure passthroughs that contribute nothing but **forgot to forward the prop**. The label is rendering `undefined`.
You *could* patch every intermediate component to pass `theme` through. But that's exactly the smell **Context** was designed to remove: when a value is needed deep in the tree by a component that doesn't care which ancestor provides it, lift it into a context.
Symptoms
- `<p id="theme">` reads "Theme: undefined" on first render
- Toggling does nothing visible
Your Task
Make the label reflect the current theme — initially `dark`, and updated when the user clicks the toggle.
> **Hint:** This challenge wants you to reach for `createContext` + `useContext` rather than threading the prop through every middle layer.
- 40. The Slow Reply - medium
Fix the Bug: The Slow Reply
This search box fires a (simulated) request every time the query changes. The problem: the API responds **faster for short queries** and slower for long ones. If you type `react` and then quickly delete down to `r`, the response for `r` arrives first, then the older `react` response lands on top of it — showing stale results.
Symptoms
- Latest query in the input, stale results on screen
- The bug is timing-dependent — it surfaces when responses arrive **out of order**
Your Task
Make sure only the **latest** request's result is written to state. Older in-flight requests should be ignored when they finally resolve.
> **Think about it:** the effect cleanup runs **before the next effect** and **on unmount**. What state can you flip in cleanup to make the older `then` a no-op?
- 41. The Read-Only Input - medium
Fix the Bug: The Read-Only Input
The input is controlled — its `value` is bound to React state. But the field rejects every keystroke. Why?
Symptoms
- Typing into `#name` does nothing
- The display below the input never updates
Your Task
Make typing work and see the display update live.
> **Think about it:** when you set `value={something}` on an input, React owns its content on every render. If state never moves, neither does the input. What’s the contract you owe React when you make an input controlled?
- 42. The Restless Effect - medium
Fix the Bug: The Restless Effect
`Logger` lists `config` in its dependency array. Yet bumping the counter causes the Logger to fire its effect over and over. Open the preview — the counter visible at the top is "how many times the Logger effect has fired since you opened the page."
Symptoms
- Click the **+** button a few times — `#fires` grows by one with each click, even though `config` is just `{ level: 'info' }` and never actually changes
Your Task
Make the Logger’s effect fire **only once on mount**, and never again from unrelated re-renders.
> **Think about it:** `{}` is a new object on every render. React’s dependency array uses `Object.is` to compare. A "shape-equal" object isn’t reference-equal. Which hook gives you a stable reference between renders?
- 43. The Lost Increment - medium
Fix the Bug: The Lost Increment
The component has two ways to add 1 to the count: a synchronous **+** button, and a **Delayed +** button that adds 1 after 200ms.
The bug: the delayed callback captures `count` at the moment it was scheduled. By the time it runs, the real count has moved on — but the callback still computes `oldCount + 1` and overwrites everything.
Symptoms
- Start at 0
- Click **Delayed +** (queues "set to 0 + 1 = 1" for 200ms later)
- Click **+** immediately (count becomes 1)
- 200ms later: count goes back to 1 instead of becoming 2
Your Task
Make every `+` truly add 1, no matter what else happened while the timer was pending.
> **Think about it:** `setCount(count + 1)` reads `count` from the closure. `setCount(prev => prev + 1)` reads from the **latest** state at update time.
- 44. The Stale Closure - easy
Fix the Bug: The Stale Closure
This counter has an "Add 3" button that calls `increment()` three times in a row. You'd expect the count to go up by 3, but it only goes up by 1.
Symptoms
- Single "Increment" click works (adds 1)
- "Add 3" button only adds 1 instead of 3
- The three calls seem to "overwrite" each other
Your Task
Fix the `increment` function so that calling it 3 times actually adds 3.
> **Think about it:** When `increment` runs, what value of `count` does it see?
- 45. JSX Basics - easy
JSX Basics
JSX looks like HTML but lives inside JavaScript. Under the hood, `<h1>Hello</h1>` becomes `React.createElement('h1', null, 'Hello')`.
Key Rules
- Use `className` instead of `class`
- Embed JavaScript expressions with curly braces `{}`
- JSX must return a **single root element** (use `<div>` or `<>` fragments)
- Self-closing tags must end with `/>` (e.g., `<img />`, `<br />`)
Your Task
Create an `App` component that renders:
1. A `<div>` wrapper containing:
- An `<h1>` with the text **"Hello, React!"**
- A `<p>` that displays the value of `const year = 2026` using a JSX expression
- A `<p>` with `className="subtitle"` containing any text
> **Hint:** Use `{year}` inside JSX to embed the variable.
- 46. Lists & Keys - easy
Lists & Keys
To render a list of items, use `Array.map()` to transform data into JSX elements.
The key prop
React needs a unique `key` for each item in a list so it can efficiently update the DOM:
```jsx
{items.map(item => (
<li key={item.id}>{item.text}</li>
))}
```
> **Important:** Never use array index as a key if the list can be reordered or filtered. Use a stable unique ID instead.
Your Task
Given this data:
```js
const todos = [
{ id: 1, text: 'Learn React', done: true },
{ id: 2, text: 'Build a project', done: false },
{ id: 3, text: 'Deploy to production', done: false },
];
```
1. Render all todos in a `<ul id="todo-list">`
2. Each `<li>` should have `data-id={todo.id}` and display the text
3. Completed todos should have `className="done"`
4. Add a button with id `"filter-btn"` that toggles between showing **all** and **active only** (not done)
5. Add a `<span id="count">` showing the number of visible items
- 47. React.memo - medium
React.memo
When a parent re-renders, **all** of its children re-render by default — even if their props are identical to last time. `React.memo` wraps a component so React skips the render when the props shallow-compare equal.
```jsx
const Child = React.memo(function Child({ name }) {
return <p>Hello, {name}</p>;
});
```
Now `<Child name="React" />` only re-runs when `name` changes — re-renders of the parent don't propagate.
> Like `useMemo`, this is a tool for measured problems. Don't blanket-wrap everything.
Your Task
Build a parent counter and a memoized greeting child:
1. Define a memoized component `Greeting` that renders `<h2 id="greeting">Hello, {name}!</h2>`
2. In `App`, hold a `count` state
3. Render `<Greeting name="React" />` and a `<p id="count">Count: {count}</p>`
4. Button `#bump` increments count by 1
- 48. Multi-step Wizard - easy
Step 2: Multi-step Wizard
Split the form into 3 steps. Only one step is visible at a time.
Requirements
1. Track current step as a number 1, 2, or 3 in state
2. Render `<p id="step-indicator">Step X of 3</p>` always
3. Step 1: `#name` input + `#next` button
4. Step 2: `#email` input + `#back` + `#next` buttons
5. Step 3: `#role` select + `#back` + `#submit` buttons
6. After submit, hide the form and render `<div id="summary">` with the values
- 49. Portals - hard
Portals
A portal renders a child into a **different DOM node** while keeping it in the React tree (state, events, context all still work).
```jsx
ReactDOM.createPortal(
<div>I render somewhere else</div>,
document.body
);
```
Why? CSS. A parent with `overflow: hidden` or a stacking context will clip or trap your modal. Portaling out to `document.body` escapes that.
Your Task
Build an open/close modal that portals out of the app:
1. Hold an `open` boolean state, initially `false`
2. `<button id="open-modal">` flips it to `true`
3. When open, render a `Modal` component using `ReactDOM.createPortal` into `document.body`
4. The modal contains:
- A container with id `"modal"`
- A `<button id="close-modal">` that flips state back to `false`
- 50. Single-page Form - easy
Step 1: Single-page Form
Build a registration form. All fields on one page; on submit, show a summary.
Requirements
1. Inputs:
- `<input id="name">` — text
- `<input id="email">` — type `"email"`
- `<select id="role">` with options "Designer", "Developer", "PM"
2. `<button id="submit">` — clicking it reveals a summary
3. Summary is a `<div id="summary">` containing the entered name, email, and role (as text)
4. Before submit, the summary should not exist in the DOM
- 51. State & useState - easy
State & useState
**State** is data that changes over time. When state changes, React **re-renders** the component to reflect the new data.
How useState Works
```jsx
const [count, setCount] = useState(0);
// ^ ^ ^
// value setter fn initial value
```
- `useState` returns an array: `[currentValue, setterFunction]`
- Calling the setter triggers a re-render
- **Never** modify state directly — always use the setter
Your Task
Build a counter component:
1. Initialize a `count` state to **0** using `useState`
2. Display the count in an `<h2>` with id **"count"**
3. Add an **Increment** button (id: `"increment"`) that increases count by 1
4. Add a **Decrement** button (id: `"decrement"`) that decreases count by 1
5. Add a **Reset** button (id: `"reset"`) that sets count back to 0
- 52. Suspense & lazy - hard
Suspense & lazy
`<React.Suspense>` lets a component **pause** while it waits for something — code, data, an image — and shows a **fallback** in the meantime.
```jsx
<React.Suspense fallback={<p>Loading...</p>}>
<Profile />
</React.Suspense>
```
Anywhere inside `<Profile>` (or deeper), if a component "throws a promise", React catches it, renders the fallback, and re-tries when the promise resolves.
In production you'd usually pair this with `React.lazy(() => import('./Profile'))` for **code splitting** — the component's code is fetched only when first rendered.
Your Task
A `greetingResource` is already created in the starter — calling `.read()` either returns the value or suspends. You don't need to touch it.
1. Build a `Greeting` component that calls `greetingResource.read()` and renders the result in `<p id="message">`
2. In `App`, wrap `<Greeting />` in `<React.Suspense>` with fallback `<p id="loading">Loading...</p>`
- 53. Toggle & Delete - easy
Step 2: Toggle & Delete
Now make todos interactive. Each `<li>` needs:
Requirements
1. A checkbox with class **`"toggle"`** that flips a `done` flag on the todo
2. When done, the `<li>` should have class **`"done"`** (you can style it later)
3. A delete button with class **`"delete"`** and text "×" that removes that todo
4. Keep the existing add behavior working
- 54. useEffect & Lifecycle - easy
useEffect & Lifecycle
`useEffect` lets you run **side effects** — things that happen outside of rendering, like updating the document title, setting up timers, or fetching data.
How It Works
```jsx
// Runs after every render
useEffect(() => { ... });
// Runs only on mount (empty deps)
useEffect(() => { ... }, []);
// Runs when "count" changes
useEffect(() => { ... }, [count]);
// Cleanup function (runs before next effect or unmount)
useEffect(() => {
const timer = setInterval(...);
return () => clearInterval(timer); // cleanup
}, []);
```
Your Task
1. Create a counter with `useState`, starting at **0**
2. Display it in `<h2 id="count">`
3. Use `useEffect` to update `document.title` to `"Count: X"` whenever count changes
4. Add an **Increment** button with id `"increment"`
5. Display a message `<p id="mounted">` that says **"Component is mounted!"** — set this text using `useEffect` with an empty dependency array and a state variable
> **Note:** The document title won't be visible in the preview, but the tests will check it!
- 55. useMemo & useCallback - medium
useMemo & useCallback
Every render creates **new objects, arrays, and functions** — even if their contents look identical. Two hooks let you skip that work when the inputs haven't changed:
useMemo — cache a computed value
```jsx
const filtered = useMemo(
() => items.filter(i => i.includes(query)),
[items, query] // recompute only when these change
);
```
useCallback — cache a function reference
```jsx
const handleChange = useCallback((e) => setQuery(e.target.value), []);
```
> **Rule of thumb:** Don't reach for these by default. Use them when you have a **measurably** expensive computation or when a child component is wrapped in `React.memo` and needs stable props.
Your Task
Build a filterable fruit list:
1. Items: `['apple', 'banana', 'cherry', 'date', 'elderberry', 'fig', 'grape']`
2. `<input id="filter">` controls the filter query
3. Use `useMemo` to compute the filtered list (case-insensitive match)
4. Use `useCallback` for the onChange handler
5. Render matches in `<ul id="results">` with one `<li>` per item
- 56. useReducer - medium
useReducer
When state updates get tangled — multiple related fields, transitions that depend on the previous state, or the same update logic scattered across handlers — `useReducer` centralizes everything into a single **reducer function**.
Shape
```jsx
const [state, dispatch] = useReducer(reducer, initialState);
function reducer(state, action) {
switch (action.type) {
case 'SET_X': return { ...state, x: action.payload };
default: return state;
}
}
dispatch({ type: 'SET_X', payload: 42 });
```
A reducer is a **pure function**: given the same state + action, it always returns the same next state. No side effects, no mutation.
Your Task
Rebuild the counter from chapter 3 with `useReducer`:
1. Initial state: `{ count: 0 }`
2. Handle action types: `'INCREMENT'`, `'DECREMENT'`, `'RESET'`
3. Display count in `<h2 id="count">`
4. Buttons `#increment`, `#decrement`, `#reset` dispatch the matching action
- 57. useRef - medium
useRef
`useRef` gives you a **mutable box** whose `.current` value sticks around between renders — and updating it does **not** trigger a re-render.
The most common job: grab a DOM node so you can call imperative APIs like `focus()`, `scrollIntoView()`, or `play()`.
Pattern
```jsx
const inputRef = useRef(null);
// 1. Attach the ref
<input ref={inputRef} />
// 2. Use the node
inputRef.current.focus();
```
Your Task
Build a search bar that focuses programmatically:
1. Create a ref with `useRef(null)`
2. Attach it to the `<input id="search">`
3. Wire `<button id="focus-btn">` so clicking it focuses the input
- 58. Accordion Behavior Bug - medium
Problem Statement
You're a frontend engineer on the CoderX docs team. The FAQ page is built around a reusable `<Accordion>` component (defined in `src/App.jsx`), but the current implementation is broken. It is supposed to behave like a classic **single-open accordion**:
- Every section renders a header `<button>` with class `accordion-trigger`.
- Every section's content panel (class `accordion-panel`) starts **collapsed**.
- Clicking a header **expands** that section's panel.
- Clicking an already-expanded header **collapses** it again.
- Opening one section **auto-closes** any other open section (at most one panel open at a time).
- Each trigger reports the correct `aria-expanded` accessibility state (`"true"` when its own panel is visible, `"false"` otherwise).
All six behaviors above are exercised by the hidden test suite — make them all pass.
The Fix
Today the accordion never reveals any content — the click handler always collapses the component back to `null`, so every panel stays closed and `aria-expanded` never changes. Rearchitect the state so that it:
1. Tracks **at most one** open index at a time (use `null` to mean "nothing open").
2. **Toggles**: clicking a closed header opens its panel; clicking the already-open header returns the accordion to "nothing open".
3. Drives both panel visibility (the `hidden` attribute on `.accordion-panel`) and the trigger's `aria-expanded` attribute from that single value.
Notes
- Don't rename `SECTIONS`, the exported `App`, or the `accordion-trigger` / `accordion-panel` class names — the tests rely on them.
- Styling is already provided in `src/App.css` — only fix the behavior, don't change the styles.
- The initial render must keep working (heading + all headers visible, no crash).
- 59. Tic-Tac-Toe Board Wiring Bug - medium
Problem Statement
You're an engineer on the CoderX Games team. Yesterday your teammate shipped the new Tic-Tac-Toe widget, and QA filed a bug ticket right away: *"Clicks don't behave — the first square seems to do nothing, and later moves look like they land in the wrong square. Games never reach a winner or a draw."*
Your job: dig through the codebase, find the wiring bug, and fix it so the game plays correctly.
The widget is split across a few modules, the same way the real feature repo is laid out:
- `src/components/Square.jsx` — renders a single playable cell (`button.square`).
- `src/components/Board.jsx` — renders the 3x3 grid of `Square` cells and reports clicks.
- `src/utils/winner.js` — `computeWinner(squares)` returns `'X'`, `'O'`, or `null`.
- `src/App.jsx` — owns the game state: the board array, whose turn it is, who won, and draw detection.
When the bug is fixed, the widget must behave like this:
1. A 3x3 board of empty cells renders on load.
2. Clicking an empty cell writes the current player's mark (`X` starts).
3. Players alternate — every click flips the turn from `X` to `O` and back.
4. A cell that already has a mark can never be overwritten (and the turn doesn't change).
5. A winning line — any row, column, or diagonal — ends the game, the status reads `Winner: X` / `Winner: O`, and further clicks are ignored.
6. When all nine cells are filled with no winner, the status reads `Draw`.
All six behaviors above are checked by the hidden test suite — make them all pass.
Notes
- Don't rename files, the exported `App`, the `square` class names, the `data-testid`s (`board`, `status`, `cell-0` … `cell-8`), or the markup structure — the tests locate cells through them.
- The win-detection and turn logic in `App.jsx` / `utils/winner.js` are correct; the defect lives in how the board reports which cell was clicked. Trace the index from a click through to `handleCellClick` before touching anything else.
- The initial render must keep working — an empty, legible 3x3 grid, no crashes.
- 60. Image Carousel Component - medium
Problem Statement
You are given a partially-built image carousel component for a React application. The carousel displays one slide at a time and provides previous/next navigation buttons as well as clickable dot indicators.
Your codebase is split into several modules:
- `src/data/slides.js` — an array of mock slide data (colored backgrounds with titles; no external images).
- `src/components/Carousel.jsx` — the main carousel component that holds state and passes props to children.
- `src/components/CarouselSlide.jsx` — renders the currently active slide.
- `src/components/CarouselControls.jsx` — renders the previous/next buttons and the dot indicators.
What you need to do
**1. Implement the navigation logic.**
The `Carousel.jsx` component has stub functions `nextSlide` and `prevSlide`. They are currently empty. Implement them so that:
- `nextSlide` advances to the next slide. When the user is on the last slide, it should **wrap around** to the first slide (index 0).
- `prevSlide` goes to the previous slide. When the user is on the first slide, it should **wrap around** to the last slide.
**2. Fix the active-dot bug.**
There is a bug in `CarouselControls.jsx` — the dot indicator that highlights the current slide is out of sync with the actual slide being displayed. The active dot does not match the slide index. Find and fix the bug so that the dot corresponding to the currently displayed slide is correctly highlighted.
Design constraints
- The carousel should display exactly one slide at a time.
- The initial slide shown should be the first slide (index 0).
- Navigation wraps around at both ends.
- Styling is minimal — just enough layout and borders to make slides and controls legible.
- 61. Star Rating Widget - medium
Problem Statement
You're a frontend engineer on the CoderX platform team. The course detail page needs a **star rating widget** so learners can rate how well a course taught the hands-on skill it promises. A teammate started the implementation (`src/components/StarRating.jsx`, backed by a custom hook plus a couple of pure helpers), but QA sent the ticket back with two open items:
1. **Clicking a star does nothing.** The widget already renders and supports hover previews, but the click-to-commit behavior was never wired up — selecting a rating is core functionality. Finish it.
2. **The hover preview leaks into the stored rating.** Hover over a star, then move the mouse away — the previewed value gets written back as the *permanent* rating instead of being thrown away. Committing a rating you only glanced at is a data-integrity bug. It lives in the rating state hook; read that file carefully.
Required behavior
- The widget renders exactly `max` stars (default `5`).
- Each star is rendered as a `<button class="star">` with a `data-testid` of `star-1` … `star-{max}`. A star at or below the currently **active** value gets the `filled` class; the rest stay empty. A plus character `★` is already used as the star glyph — leave it.
- The widget root is `<div class="star-rating">`, and it always exposes the **committed** rating via a `data-rating` attribute.
- Given an `initialValue` prop (default `0`), exactly that many stars render filled on first paint.
- Clicking star *N* permanently sets the rating to *N*, clamped between `0` and `max`, and clears any in-progress hover preview.
- Hovering star *N* only *previews* it: the stars light up to *N* while the pointer is over the row, but the committed `data-rating` must not change.
- Moving the mouse off the star row discards the preview and reverts the display to the committed rating.
Files
- `src/components/Star.jsx` — presentational star button. Already implemented; read it to learn its props.
- `src/components/StarRating.jsx` — container that wires stars to state. `handleClick` is a stub — implement it.
- `src/hooks/useStarRating.js` — rating / hover state. `commit` is a stub (implement it) **and** this file also contains the QA bug above (find and fix it). Don't confuse the two — fix the bug without "fixing" the todo.
- `src/utils/rating.js` — pure helpers `clampRating` and `getStarState`. Already implemented.
- `src/App.jsx` — page shell that renders the widget.
Notes
- The hidden test suite drives everything through `data-testid`, the `.star-rating` root, its `data-rating` attribute, and the `.star` / `.filled` classes — don't rename any of these.
- Hover events fire on the star buttons; the "pointer left the row" handler is `onMouseLeave` on `.star-rating__row`. Tests simulate those events directly.
- Keep styling minimal and functional — no new styling libraries, themes, or animations.
- The initial preview must stay rendering (heading + stars, no crash) the moment you open the challenge.
- 62. Tweet Component Like & Retweet Bug Hunt - hard
Problem Statement
You're an engineer on the CoderX social team. Your teammate opened a PR adding a `Tweet` component to the home feed widget, but QA has filed two tickets against it:
1. **The like button doesn't work.** Clicking "Like" does nothing — the count stays put and the button never shows an active state.
2. **Un-retweeting sends the count off a cliff.** Retweeting then un-retweeting a post ends with a retweet count that keeps shrinking — QA watched the counter keep going down as they toggled it on and off.
Your job: dig through the codebase, implement the missing like logic, and fix the retweet counting bug so the widget behaves correctly.
The module layout mirrors a real feature repo — open everything before you change anything:
- `src/data/mockTweet.js` — the in-memory tweet data (no API or backend; it's all local).
- `src/hooks/useLike.js` — owns the `liked` / `likeCount` state. **`toggleLike` is an unimplemented stub — this is the main piece you need to write.**
- `src/hooks/useRetweet.js` — owns the `retweeted` / `retweetCount` state. **Contains the retweet counting bug QA found — read it and fix it.**
- `src/components/TweetActions.jsx` — renders the Reply / Retweet / Like buttons with their counts and active-state styling. Already wired to the hook callbacks.
- `src/components/Tweet.jsx` — the tweet card: author, handle, content, and the actions row.
- `src/App.jsx` — renders the feed page shell.
When everything is fixed, the component must behave like this:
1. The card renders the author name, handle, and content, plus the initial reply / retweet / like counts from the mock data.
2. Clicking **Like** increments the like count by 1 and marks the button as liked (`aria-pressed="true"` + `is-active` class).
3. Clicking **Like** again un-likes the tweet: the count decrements back by 1 (never below 0), and the active/liked state clears.
4. Clicking **Retweet** increments the retweet count by 1 and marks the button as retweeted; clicking it again un-retweets and decrements back by exactly 1 — never 2, and never below 0.
5. Counts must never go below 0, no matter how many times an action is toggled on and off.
6. The buttons' `aria-pressed` / `is-active` state always matches the current liked / retweeted status.
All six behaviors are exercised by the hidden test suite — make them all pass.
Notes
- Don't rename files, exported functions, `data-testid`s (`tweet-author-name`, `tweet-author-handle`, `tweet-content`, `reply-count`, `retweet-count`, `like-count`, `like-button`, `retweet-button`), the `is-active` class, or `aria-pressed` — the tests locate everything through them.
- `TweetActions.jsx`, `Tweet.jsx`, and `mockTweet.js` are correct — the defect lives in the hooks. Trace the like click from the button through `Tweet.jsx` into `useLike.js`, and double-check the retweet arithmetic in `useRetweet.js`.
- Styling is intentionally minimal; only behavior matters for passing the tests.
- 63. Analog Clock - medium
Problem Statement
A teammate left you a **half-finished analog clock** widget. The preview renders, but the hands do not behave correctly:
1. The **second hand never appears** - its rotation angle was never implemented (`getHandAngles` still returns the placeholder `null`).
2. The **hour hand jumps** between hour marks instead of gliding smoothly - it ignores how many minutes have elapsed since the top of the hour.
Your job: make every hand point where it should for any given time, then confirm the preview shows a working clock.
Codebase tour
- `src/utils/getHandAngles.js` - the heart of the exercise. A pure function that converts a `Date` into hand rotation angles: it returns `{ hourDeg, minuteDeg, secondDeg }`, each measured **clockwise from 12 o'clock** in the range `[0, 360)`. Nothing here touches the DOM or timers, so the whole math can be unit-tested directly with fixed `Date` values.
- `src/components/AnalogClock.jsx` - renders the SVG clock face and rotates each hand using `getHandAngles`. The second hand is only painted once its angle is a real number.
- `src/components/DigitalReadout.jsx` + `src/utils/formatTime.js` - a plain `HH:MM:SS` readout beneath the clock.
- `src/hooks/useNow.js` - keeps `now` ticking once per second; `src/App.jsx` wires it all together.
Rotation math (reference)
- **Second hand**: 360 degrees in 60 seconds -> **6 degrees per second**.
- **Minute hand**: 360 degrees in 60 minutes -> **6 degrees per minute**.
- **Hour hand**: 360 degrees in 12 hours -> **30 degrees per hour**, **plus 0.5 degrees per elapsed minute** so it glides between hour marks (e.g. at 1:30 it should point at **45 degrees**, halfway between the 1 and 2 marks).
`Date#getHours()` is 0-23, so use `hours % 12` so that 12 and 24 behave as 0 on the dial. At `12:00:00` every hand must return `0`, never 360.
What to do
- **Implement** the second hand rotation in `getHandAngles.js` (replace the `null` stub with the real formula).
- **Fix** the hour hand so it advances fractionally with elapsed minutes.
Acceptance criteria
- `npm test` (Vitest) passes - the suite tests `getHandAngles` with fixed dates plus a smoke test that the preview still mounts.
- The preview renders a working analog clock; once all tests pass, the second hand is visible and the hour hand glides smoothly.
- 64. Tunely Songs API: Playlists, Queue & Library - hard
Problem Statement
Welcome to **Tunely** 🎵 - a small music-streaming startup. The mobile team is about to ship the **player screen**, and they need the backend's `Songs API` ready. You've just been added to the repo.
The API is a **mock**: there are **no audio files**, no uploads and **no authentication**. Songs are just metadata from a fake catalog, and everything lives **in memory** (restart the server and it resets).
Part of the API was never written, and part of what *was* written is misbehaving. The project is split across several files like a real codebase, so expect to read, trace and fix across modules.
Your job has two parts:
- **Part A - Build:** implement 4 endpoints from scratch.
- **Part B - Debug:** QA filed several bug reports against endpoints that already exist. Find the root causes and fix them.
> 💡 A failing test does **not** always mean the handler under test is wrong. Read the failure message, then follow the request through `routes → controllers → utils → db`.
---
Data model
**Song** (never modified by the API)
```json
{ "id": 1, "title": "Neon Horizon", "artist": "The Midnight Owls", "album": "City Lights", "durationSec": 215, "genre": "synthwave" }
```
**Playlist** is stored as `{ id, name, description, songIds }`. A playlist **detail** response looks like:
```json
{
"id": 1,
"name": "Chill Vibes",
"description": "Easy listening for slow afternoons",
"songs": [ /* full Song objects, in playlist order */ ],
"songCount": 8,
"totalDurationSec": 1681
}
```
**Library** is stored as `{ id, name, songIds }`. A library **detail** response is `{ id, name, songs, songCount }`.
**Queue** is a single global player queue (there are no users). Its response shape is:
```json
{ "nowPlaying": { /* Song */ } , "upNext": [ /* Song, Song, ... */ ] }
```
`nowPlaying` is `null` when nothing is playing.
**Seed data** (restored by `resetDb()` before every test): playlist `1` "Chill Vibes" with songs `1-8`, playlist `2` "Road Trip" with songs `9, 10, 11`, library `1` "My Library" with songs `1, 2`, and an empty queue.
Use the helpers in `utils/` (`formatters`, `finders`, `validators`) and `db.nextPlaylistId()` / `db.nextLibraryId()` rather than reinventing them.
**Error format:** every error is JSON shaped like `{ "error": "message" }`.
---
Part A - Build these endpoints (currently return `501 Not Implemented`)
1. `POST /api/playlists` - create a playlist
Body: `{ "name": string, "description"?: string, "songIds"?: number[] }`
- `name` is required and must be a non-blank string, otherwise `400`.
- `description` defaults to `""`, `songIds` defaults to `[]` and the order of `songIds` is preserved.
- `songIds` must be an array of **existing** song ids, otherwise `400` and nothing is created.
- Each new playlist gets a unique id from `db.nextPlaylistId()`.
- Success: `201` with the **playlist detail** shape.
2. `DELETE /api/playlists/:id` - delete a playlist
- Success: `204` with an empty body. The playlist is gone from `GET /api/playlists` and `GET /api/playlists/:id` returns `404`.
- Unknown playlist: `404`.
3. `POST /api/queue/add` - add to queue
Body: `{ "songId": number }`
- Appends the song to the **end** of `upNext`.
- `songId` must be a positive integer, otherwise `400`. An unknown song gives `404`. In both cases the queue must stay unchanged.
- Success: `201` with the **queue** shape.
4. `POST /api/libraries` - create a library
Body: `{ "name": string }`
- `name` is required and must be a non-blank string, otherwise `400` and nothing is created.
- Each new library gets a unique id from `db.nextLibraryId()` and starts empty.
- Success: `201` with the **library detail** shape.
---
Part B - Fix these bug reports
These endpoints already exist, but QA reports they don't behave according to the contract below. The reports only describe **symptoms**, so finding the cause is part of the task.
| Ticket | Endpoint | What QA saw |
|---|---|---|
| TUN-101 | `GET /api/playlists/:id` and other `/:id` playlist routes | "Playlists that clearly exist come back as *not found*." |
| TUN-102 | `PUT /api/playlists/:id` | "I can rename a playlist, but I can also change its `id` and wipe its songs by sending extra fields. Sending an empty name is accepted too." |
| TUN-103 | `POST /api/playlists/:id/shuffle` | "After shuffling, the playlist itself is permanently reordered. Shuffle should only affect what is *returned*." |
| TUN-104 | `POST /api/queue/play-next` | "Songs added with *Play next* show up at the back of the queue instead of playing next." |
| TUN-105 | `POST /api/libraries/:id/songs` | "I can add songs that don't exist, and the same song can be added to a library again and again." |
Expected behaviour (the contract)
**`PUT /api/playlists/:id`** - body `{ "name"?: string, "description"?: string }`
- Only `name` and `description` can change. `id` and `songIds` must never be changed by the request body.
- If `name` is provided it must be a non-blank string, otherwise `400` and the playlist stays untouched.
- Success: `200` with the playlist detail. Unknown playlist: `404`.
**`POST /api/playlists/:id/shuffle`** - no body
- Returns `200` with `{ "playlistId": number, "songs": Song[] }`, containing every song of the playlist exactly once in a random order.
- The stored order of the playlist is **not** modified. Unknown playlist: `404`.
**`POST /api/queue/play-next`** - body `{ "songId": number }`
- Inserts the song at the **front** of `upNext`, so the most recent *play next* plays first.
- Validation matches `/api/queue/add`: `400` for an invalid `songId`, `404` for an unknown song.
- Success: `201` with the queue shape.
**`POST /api/libraries/:id/songs`** - body `{ "songId": number }`
- Checks run in this order: library not found `404`, invalid `songId` `400`, song not found `404`, song already in the library `409`.
- Success: `201` with the library detail. A rejected request must leave the library unchanged.
---
Already implemented (should keep working)
| Method | Path | Notes |
|---|---|---|
| `GET` | `/api/songs` | Optional filters `?genre=` and `?artist=` (case-insensitive) |
| `GET` | `/api/songs/:id` | `404` when unknown |
| `GET` | `/api/playlists` | Summaries: `{ id, name, description, songCount }` |
| `POST` | `/api/playlists/:id/songs` | Body `{ songId }`, appends to the playlist, returns playlist detail `201` |
| `GET` | `/api/queue` | Current queue |
| `POST` | `/api/queue/next` | Moves the first song of `upNext` to `nowPlaying`, or sets `nowPlaying` to `null` when `upNext` is empty |
| `GET` | `/api/libraries` | Summaries: `{ id, name, songCount }` |
| `GET` | `/api/libraries/:id` | Library detail, `404` when unknown |
---
Try it yourself
Start the server in the terminal with `node index.js` (it listens on port `3000`) and poke at it with `curl`:
```bash
curl localhost:3000/api/songs?genre=rock
curl -X POST localhost:3000/api/queue/add -H "Content-Type: application/json" -d '{"songId": 3}'
curl localhost:3000/api/queue
```
Restart the server any time to reset the in-memory data.
---
Rules & tips
- Keep the response shapes and status codes exactly as described above - the tests check them.
- Don't add authentication, a real database or audio handling. Everything stays in memory.
- You may refactor and add helpers, but keep the existing route paths.
- Tests are grouped per resource. Start with the first failing test of a group and read its message carefully.
- Fixing a shared helper can turn several red tests green at once. 🟢