639 lines
15 KiB
Markdown
639 lines
15 KiB
Markdown
# DevDash - Super Hard Debug Challenge (16-18 Errors)
|
|
|
|
## Challenge Overview
|
|
|
|
This is the **super hard tier** debug challenge with 16-18 intentional errors distributed across 6 categories. Students must identify and fix all errors to make the project run successfully.
|
|
|
|
---
|
|
|
|
## CATEGORY 1: BLOCKING ERRORS (3 Errors)
|
|
|
|
### Error 1: Missing Dependency - `date-fns`
|
|
|
|
**File:** `src/lib/utils.ts`
|
|
|
|
**Issue:** The code imports `format` from `date-fns`:
|
|
```typescript
|
|
import { format } from "date-fns";
|
|
```
|
|
|
|
But `date-fns` is **NOT** listed in `package.json`.
|
|
|
|
**Symptom:** Runtime error: "Cannot find module 'date-fns'"
|
|
|
|
**Fix:** Add to `package.json` dependencies:
|
|
```json
|
|
"dependencies": {
|
|
"date-fns": "^2.30.0"
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### Error 2: Wrong Import Path - `next/navigations` (with 's')
|
|
|
|
**File:** `src/components/Header.tsx`
|
|
|
|
**Issue:**
|
|
```typescript
|
|
import { usePathname } from "next/navigations"; // WRONG - has 's'
|
|
```
|
|
|
|
Should be:
|
|
```typescript
|
|
import { usePathname } from "next/navigation"; // CORRECT
|
|
```
|
|
|
|
**Symptom:** Module not found error at build time
|
|
|
|
**Fix:** Remove the 's' from the import path.
|
|
|
|
---
|
|
|
|
### Error 3: Circular Dependency Crash
|
|
|
|
**Files:** `src/lib/types.ts` and `src/lib/utils.ts`
|
|
|
|
**Issue:**
|
|
|
|
In `src/lib/utils.ts`:
|
|
```typescript
|
|
import { formatDate } from "./utils"; // Value import, not type
|
|
```
|
|
|
|
In `src/lib/types.ts`:
|
|
```typescript
|
|
import { formatDate } from "./utils";
|
|
|
|
export function createPost(data: Omit<Post, "formattedDate">): Post {
|
|
return { ...data, formattedDate: formatDate(data.date) }; // formatDate is undefined!
|
|
}
|
|
```
|
|
|
|
**Symptom:** `TypeError: formatDate is not a function` at runtime
|
|
|
|
**Fix:** Use `import type` for types to avoid the circular dependency:
|
|
|
|
In `src/lib/utils.ts`:
|
|
```typescript
|
|
import type { Post } from "./types"; // Change to type import
|
|
```
|
|
|
|
Alternatively, restructure to avoid circular imports altogether.
|
|
|
|
---
|
|
|
|
## CATEGORY 2: NEXT.JS SPECIFIC ERRORS (4 Errors)
|
|
|
|
### Error 4: Missing "use client" Directive
|
|
|
|
**File:** `src/components/ThemeToggle.tsx`
|
|
|
|
**Issue:** The component uses `useState` (a client-side hook) but **lacks** `"use client"` at the top.
|
|
|
|
```typescript
|
|
// MISSING "use client" at the top!
|
|
import { useState } from 'react';
|
|
export function ThemeToggle() {
|
|
const [isHovered, setIsHovered] = useState(false);
|
|
// ...
|
|
}
|
|
```
|
|
|
|
**Symptom:** Next.js error: "You're importing a component that needs useState. It only works in Client Components."
|
|
|
|
**Fix:** Add at the very top of the file:
|
|
```typescript
|
|
"use client";
|
|
|
|
import { useState } from 'react';
|
|
```
|
|
|
|
---
|
|
|
|
### Error 5: Hooks in Server Component
|
|
|
|
**File:** `src/app/dashboard/page.tsx`
|
|
|
|
**Issue:** The page uses `useState` and `useEffect` directly:
|
|
```typescript
|
|
// MISSING "use client" at the top!
|
|
import { useState, useEffect } from 'react';
|
|
|
|
export default function DashboardPage() {
|
|
const [isLoading, setIsLoading] = useState(false);
|
|
// ...
|
|
}
|
|
```
|
|
|
|
**Symptom:** Hydration error or "You're importing a component that needs useState"
|
|
|
|
**Fix:** Add `"use client"` directive at the top:
|
|
```typescript
|
|
"use client";
|
|
|
|
import { useState, useEffect } from 'react';
|
|
```
|
|
|
|
---
|
|
|
|
### Error 6: Wrong Middleware Matcher Pattern
|
|
|
|
**File:** `middleware.ts`
|
|
|
|
**Issue:**
|
|
```typescript
|
|
export const config = {
|
|
matcher: ['/dashboard/:path'], // WRONG - missing asterisk
|
|
};
|
|
```
|
|
|
|
This matches `/dashboard/:path` but NOT `/dashboard/page/subpage`. The sub-routes won't be protected.
|
|
|
|
Also, the middleware looks for `'auth-token'` cookie but the app doesn't set one:
|
|
```typescript
|
|
const authToken = request.cookies.get('auth-token')?.value;
|
|
```
|
|
|
|
**Symptom:** Sub-routes of dashboard bypass middleware; authentication doesn't work
|
|
|
|
**Fix:**
|
|
```typescript
|
|
export const config = {
|
|
matcher: ['/dashboard/:path*'], // Add asterisk for all sub-routes
|
|
};
|
|
```
|
|
|
|
And update cookie name to match what's actually used or create a mock auth mechanism.
|
|
|
|
---
|
|
|
|
### Error 7: `metadata` Export in Client Component
|
|
|
|
**File:** `src/app/blog/[slug]/page.tsx`
|
|
|
|
**Issue:**
|
|
```typescript
|
|
'use client'; // This is a CLIENT component
|
|
|
|
export const metadata = { // Can't export metadata from client!
|
|
title: 'Blog Post',
|
|
description: 'Read the full article',
|
|
};
|
|
|
|
import { useParams } from 'next/navigation';
|
|
```
|
|
|
|
**Symptom:** Build error: "Metadata exports must be in a Server Component"
|
|
|
|
**Fix (Option 1):** Remove `'use client'` and use function parameters instead of `useParams()`:
|
|
```typescript
|
|
// Remove "use client"
|
|
import { generateMetadata } from 'next';
|
|
import type { Metadata } from 'next';
|
|
|
|
export async function generateMetadata({ params }): Promise<Metadata> {
|
|
const post = getPostBySlug(params.slug);
|
|
return {
|
|
title: post?.title || 'Blog Post',
|
|
description: post?.content.substring(0, 100) || '',
|
|
};
|
|
}
|
|
|
|
export default function BlogPostPage({ params }: { params: { slug: string } }) {
|
|
const post = getPostBySlug(params.slug);
|
|
// ...
|
|
}
|
|
```
|
|
|
|
**Fix (Option 2):** Keep as client component and remove metadata export:
|
|
```typescript
|
|
'use client';
|
|
|
|
// Remove metadata export
|
|
|
|
import { useParams } from 'next/navigation';
|
|
// ... rest of component
|
|
```
|
|
|
|
---
|
|
|
|
## CATEGORY 3: LOGIC ERRORS (3 Errors)
|
|
|
|
### Error 8: Wrong Filter Condition
|
|
|
|
**File:** `src/data/posts.ts`
|
|
|
|
**Issue:**
|
|
```typescript
|
|
export function getPublishedPosts(): Post[] {
|
|
return posts.filter(p => p.draft); // WRONG - returns DRAFTS!
|
|
}
|
|
```
|
|
|
|
Should check for `!p.draft` (NOT drafted):
|
|
```typescript
|
|
export function getPublishedPosts(): Post[] {
|
|
return posts.filter(p => !p.draft); // CORRECT
|
|
}
|
|
```
|
|
|
|
**Symptom:** Blog page shows only draft posts (only 1 post visible instead of 2 published ones)
|
|
|
|
**Fix:** Change condition to `!p.draft`.
|
|
|
|
---
|
|
|
|
### Error 9: Stale Closure in Event Handler
|
|
|
|
**File:** `src/components/Testimonials.tsx`
|
|
|
|
**Issue:**
|
|
```typescript
|
|
useEffect(() => {
|
|
const timer = setInterval(() => {
|
|
setCurrentIndex(currentIndex + 1); // STALE CLOSURE!
|
|
}, 4000);
|
|
return () => clearInterval(timer);
|
|
}, []); // Empty dependency array captures initial value
|
|
```
|
|
|
|
The `currentIndex` is captured from the initial render (0). The interval always increments from 0 to 1, causing the carousel to jump back repeatedly.
|
|
|
|
**Symptom:** Testimonial carousel doesn't advance smoothly; always jumps to position 1
|
|
|
|
**Fix:** Use the functional update form of `setState`:
|
|
```typescript
|
|
useEffect(() => {
|
|
const timer = setInterval(() => {
|
|
setCurrentIndex(prev => (prev + 1) % testimonials.length); // Use prev!
|
|
}, 4000);
|
|
return () => clearInterval(timer);
|
|
}, []);
|
|
```
|
|
|
|
---
|
|
|
|
### Error 10: Using Index as Key
|
|
|
|
**File:** `src/components/Features.tsx`
|
|
|
|
**Issue:**
|
|
```typescript
|
|
{displayedFeatures.map((feature, index) => (
|
|
<div key={index}> // WRONG - using index as key
|
|
{/* ... */}
|
|
</div>
|
|
))}
|
|
```
|
|
|
|
Using index as key is problematic if the list reorders or filters.
|
|
|
|
**Symptom:** Visual glitches when filtering features; component state gets mixed up
|
|
|
|
**Fix:** Use unique identifier:
|
|
```typescript
|
|
{displayedFeatures.map((feature) => (
|
|
<div key={feature.id}> // Use feature.id
|
|
{/* ... */}
|
|
</div>
|
|
))}
|
|
```
|
|
|
|
---
|
|
|
|
## CATEGORY 4: TYPESCRIPT ERRORS (2 Errors)
|
|
|
|
### Error 11: Wrong API Response Type
|
|
|
|
**File:** `src/app/api/stats/route.ts` and `src/components/StatsGrid.tsx`
|
|
|
|
**Issue:**
|
|
|
|
API returns wrapped response:
|
|
```typescript
|
|
// src/app/api/stats/route.ts
|
|
return NextResponse.json({ data: stats }); // Wrapped in { data: ... }
|
|
```
|
|
|
|
But component expects unwrapped array:
|
|
```typescript
|
|
// src/components/StatsGrid.tsx
|
|
const data = await response.json();
|
|
setStats(data); // Expects data to be array, but gets { data: [...] }
|
|
```
|
|
|
|
Later:
|
|
```typescript
|
|
{stats.map((stat, index) => ( // stats is an object, not array!
|
|
```
|
|
|
|
**Symptom:** TypeError: `stats.map is not a function`
|
|
|
|
**Fix - Option 1:** Unwrap in component:
|
|
```typescript
|
|
const data = await response.json();
|
|
setStats(data.data); // Extract the array
|
|
```
|
|
|
|
**Fix - Option 2:** Change API response:
|
|
```typescript
|
|
return NextResponse.json(stats); // Return array directly
|
|
```
|
|
|
|
---
|
|
|
|
### Error 12: Missing Optional Chaining
|
|
|
|
**File:** `src/components/UserProfile.tsx`
|
|
|
|
**Issue:**
|
|
```typescript
|
|
{user.profile.avatar} // profile could be undefined!
|
|
```
|
|
|
|
The API might return a user without a `profile` object:
|
|
```typescript
|
|
const user: User = {
|
|
id: '1',
|
|
name: 'John',
|
|
email: 'john@example.com',
|
|
// profile is undefined!
|
|
};
|
|
```
|
|
|
|
**Symptom:** TypeError: `Cannot read property 'avatar' of undefined`
|
|
|
|
**Fix:** Use optional chaining:
|
|
```typescript
|
|
{user.profile?.avatar} // Safe access
|
|
```
|
|
|
|
Or add a null check:
|
|
```typescript
|
|
{user.profile && user.profile.avatar}
|
|
```
|
|
|
|
---
|
|
|
|
## CATEGORY 5: REACT PATTERN ERRORS (3 Errors)
|
|
|
|
### Error 13: Missing useEffect Cleanup
|
|
|
|
**File:** `src/app/dashboard/page.tsx`
|
|
|
|
**Issue:**
|
|
```typescript
|
|
useEffect(() => {
|
|
window.addEventListener('resize', handleResize);
|
|
// Missing cleanup!
|
|
// Should have: return () => window.removeEventListener('resize', handleResize);
|
|
}, []);
|
|
```
|
|
|
|
**Symptom:** Memory leak; listeners accumulate when component remounts; "Can't perform a React state update on an unmounted component" warnings
|
|
|
|
**Fix:** Add cleanup function:
|
|
```typescript
|
|
useEffect(() => {
|
|
function handleResize() {
|
|
setIsLoading(window.innerWidth < 768);
|
|
}
|
|
|
|
window.addEventListener('resize', handleResize);
|
|
|
|
// Add cleanup
|
|
return () => {
|
|
window.removeEventListener('resize', handleResize);
|
|
};
|
|
}, []);
|
|
```
|
|
|
|
---
|
|
|
|
### Error 14: Incorrect Context Provider Wrapping
|
|
|
|
**File:** `src/app/layout.tsx`
|
|
|
|
**Issue:**
|
|
```typescript
|
|
<body>
|
|
<Header /> {/* Header tries to use theme context but provider hasn't wrapped it yet! */}
|
|
<ThemeProvider>
|
|
<main>
|
|
{children}
|
|
</main>
|
|
<Footer />
|
|
</ThemeProvider>
|
|
</body>
|
|
```
|
|
|
|
The `Header` component is rendered BEFORE the `ThemeProvider`, so it can't access the theme context.
|
|
|
|
**Symptom:** Error in Header when trying to use `useTheme()`: "useTheme must be used within ThemeProvider"
|
|
|
|
**Fix:** Move provider to wrap everything:
|
|
```typescript
|
|
<body>
|
|
<ThemeProvider>
|
|
<Header />
|
|
<main>
|
|
{children}
|
|
</main>
|
|
<Footer />
|
|
</ThemeProvider>
|
|
</body>
|
|
```
|
|
|
|
---
|
|
|
|
### Error 15: Prop Drilling Mistake - Wrong Prop Name
|
|
|
|
**File:** `src/app/blog/page.tsx` and `src/components/BlogCard.tsx`
|
|
|
|
**Issue:**
|
|
|
|
BlogCard expects `post`:
|
|
```typescript
|
|
// src/components/BlogCard.tsx
|
|
export function BlogCard({ post }: { post: Post }) {
|
|
return (
|
|
<article>
|
|
<h3>{post?.title || 'Untitled'}</h3>
|
|
{/* ... */}
|
|
</article>
|
|
);
|
|
}
|
|
```
|
|
|
|
But blog page passes `blogPost`:
|
|
```typescript
|
|
// src/app/blog/page.tsx
|
|
{posts.map((post) => (
|
|
<BlogCard key={post.slug} blogPost={post} /> // WRONG prop name!
|
|
))}
|
|
```
|
|
|
|
**Symptom:** BlogCard displays "Untitled" and "Date unknown"; content is empty
|
|
|
|
**Fix:** Use correct prop name:
|
|
```typescript
|
|
{posts.map((post) => (
|
|
<BlogCard key={post.slug} post={post} /> // Correct
|
|
))}
|
|
```
|
|
|
|
---
|
|
|
|
## CATEGORY 6: STYLING ERRORS (3 Errors)
|
|
|
|
### Error 16: Custom CSS Classes Instead of Tailwind
|
|
|
|
**File:** `src/app/globals.css`
|
|
|
|
**Issue:** The stylesheet defines custom classes:
|
|
```css
|
|
.container {
|
|
max-width: 1200px;
|
|
margin: 0 auto;
|
|
padding: 0 1rem;
|
|
}
|
|
|
|
.card {
|
|
background-color: white;
|
|
border-radius: 0.5rem;
|
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
|
padding: 1.5rem;
|
|
}
|
|
|
|
.btn-primary {
|
|
background-color: #3b82f6;
|
|
color: white;
|
|
/* ... more styles ... */
|
|
}
|
|
```
|
|
|
|
In a Tailwind project, these should be Tailwind classes, not custom CSS.
|
|
|
|
**Symptom:** Custom classes work but don't align with Tailwind approach; styling is scattered between CSS and inline styles
|
|
|
|
**Fix:** Replace with Tailwind classes or @apply directives:
|
|
```css
|
|
@layer components {
|
|
.container {
|
|
@apply max-w-5xl mx-auto px-4;
|
|
}
|
|
|
|
.card {
|
|
@apply bg-white rounded-lg shadow p-6;
|
|
}
|
|
|
|
.btn-primary {
|
|
@apply bg-blue-500 text-white px-6 py-3 rounded transition-colors hover:bg-blue-600;
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### Error 17-18: Inline Styles Instead of Tailwind Classes
|
|
|
|
**Files:** Multiple components use inline `style={{}}` instead of Tailwind:
|
|
|
|
- `src/components/Header.tsx` - All styling is inline
|
|
- `src/components/Hero.tsx` - All styling is inline
|
|
- `src/components/Features.tsx` - All styling is inline
|
|
- `src/components/Testimonials.tsx` - All styling is inline
|
|
- `src/components/StatsGrid.tsx` - All styling is inline
|
|
- `src/components/BlogCard.tsx` - All styling is inline
|
|
- `src/components/UserProfile.tsx` - All styling is inline
|
|
- And more...
|
|
|
|
**Example Issue:**
|
|
```typescript
|
|
<header style={{
|
|
backgroundColor: '#ffffff',
|
|
borderBottom: '1px solid #e5e7eb',
|
|
padding: '1rem 0'
|
|
}}>
|
|
{/* ... */}
|
|
</header>
|
|
```
|
|
|
|
Should be:
|
|
```typescript
|
|
<header className="bg-white border-b border-gray-200 py-4">
|
|
{/* ... */}
|
|
</header>
|
|
```
|
|
|
|
**Symptom:** Project uses Tailwind but styling isn't using Tailwind; inconsistent; not maintainable
|
|
|
|
**Fix:** Convert all inline styles to Tailwind classes. Example conversions:
|
|
|
|
```
|
|
backgroundColor: '#ffffff' → bg-white
|
|
borderBottom: '1px solid #e5e7eb' → border-b border-gray-200
|
|
padding: '1rem 0' → py-4
|
|
fontSize: '1.5rem' → text-2xl
|
|
fontWeight: 'bold' → font-bold
|
|
display: 'flex' → flex
|
|
gap: '1rem' → gap-4
|
|
justifyContent: 'center' → justify-center
|
|
alignItems: 'center' → items-center
|
|
```
|
|
|
|
---
|
|
|
|
## Summary Table
|
|
|
|
| # | Category | Error | File | Severity |
|
|
|---|----------|-------|------|----------|
|
|
| 1 | Blocking | Missing `date-fns` | `lib/utils.ts` | CRITICAL |
|
|
| 2 | Blocking | Wrong import `next/navigations` | `components/Header.tsx` | CRITICAL |
|
|
| 3 | Blocking | Circular dependency | `lib/types.ts`, `lib/utils.ts` | CRITICAL |
|
|
| 4 | Next.js | Missing "use client" | `components/ThemeToggle.tsx` | CRITICAL |
|
|
| 5 | Next.js | Hooks in server component | `app/dashboard/page.tsx` | CRITICAL |
|
|
| 6 | Next.js | Wrong matcher pattern | `middleware.ts` | HIGH |
|
|
| 7 | Next.js | Metadata in client component | `app/blog/[slug]/page.tsx` | CRITICAL |
|
|
| 8 | Logic | Wrong filter condition | `data/posts.ts` | MEDIUM |
|
|
| 9 | Logic | Stale closure | `components/Testimonials.tsx` | MEDIUM |
|
|
| 10 | Logic | Index as key | `components/Features.tsx` | MEDIUM |
|
|
| 11 | TypeScript | API response type mismatch | `api/stats/route.ts`, `components/StatsGrid.tsx` | HIGH |
|
|
| 12 | TypeScript | Missing optional chaining | `components/UserProfile.tsx` | MEDIUM |
|
|
| 13 | React | Missing useEffect cleanup | `app/dashboard/page.tsx` | HIGH |
|
|
| 14 | React | Wrong provider wrapping | `app/layout.tsx` | CRITICAL |
|
|
| 15 | React | Wrong prop name | `app/blog/page.tsx`, `components/BlogCard.tsx` | MEDIUM |
|
|
| 16 | Styling | Custom CSS classes | `app/globals.css` | LOW |
|
|
| 17-18 | Styling | Inline styles (8+ components) | Multiple components | LOW |
|
|
|
|
---
|
|
|
|
## Debugging Strategy
|
|
|
|
1. **Start with blocking errors** (1-3) - These prevent the app from running at all
|
|
2. **Fix Next.js errors** (4-7) - These cause build-time failures
|
|
3. **Address logic errors** (8-10) - These cause runtime misbehavior
|
|
4. **Resolve TypeScript errors** (11-12) - These break specific features
|
|
5. **Fix React patterns** (13-15) - These cause memory leaks and state issues
|
|
6. **Clean up styling** (16-18) - These improve code quality and maintainability
|
|
|
|
---
|
|
|
|
## Testing Checklist
|
|
|
|
After fixing all errors:
|
|
|
|
- [ ] App builds without errors: `npm run build`
|
|
- [ ] Dev server runs: `npm run dev`
|
|
- [ ] Homepage loads with Hero, Features, Testimonials
|
|
- [ ] All navigation links work
|
|
- [ ] Blog page shows published posts (2 posts, not drafts)
|
|
- [ ] Blog post pages load correctly
|
|
- [ ] Dashboard page loads with stats and user profile
|
|
- [ ] Theme toggle works (if fixed)
|
|
- [ ] Carousel auto-advances smoothly
|
|
- [ ] No console errors
|
|
- [ ] No memory leaks in browser dev tools
|
|
|