Files
novi-lessons/Samenvattingen/Les07-Samenvatting.md
2026-01-30 11:56:39 +01:00

413 lines
8.0 KiB
Markdown

# Les 7: Next.js Fundamentals 2 - API Routes & Data Fetching
---
## Hoofdstuk
**Deel 2: Technical Foundations** (Les 5-9)
## Beschrijving
Leer data fetching in Next.js: Server Components, Client Components, API routes en React Query. Bouw een volledig werkende app met data.
---
## Te Behandelen
### Server Components vs Client Components
**Server Components (default in Next.js):**
- Renderen op de server
- Geen JavaScript naar de browser
- Kunnen direct data fetchen (async/await)
- Kunnen NIET: useState, useEffect, event handlers
**Client Components:**
- Renderen in de browser
- JavaScript naar de browser
- Kunnen interactief zijn
- Markeer met `'use client'` bovenaan
---
### Wanneer Wat?
```
Server Component → Data tonen, geen interactie
Client Component → Interactie nodig (forms, buttons, state)
```
**Voorbeeld:**
```tsx
// Server Component - data ophalen
async function ProductList() {
const products = await fetchProducts() // Direct fetchen!
return <ul>{products.map(p => <li>{p.name}</li>)}</ul>
}
// Client Component - interactie
'use client'
function AddToCartButton({ productId }) {
const [added, setAdded] = useState(false)
return <button onClick={() => setAdded(true)}>Add</button>
}
```
---
### Data Fetching in Server Components
Simpelweg `async/await` gebruiken:
```tsx
// app/products/page.tsx
interface Product {
id: number
name: string
price: number
}
async function getProducts(): Promise<Product[]> {
const res = await fetch('https://api.example.com/products')
return res.json()
}
export default async function ProductsPage() {
const products = await getProducts()
return (
<div>
<h1>Producten</h1>
<ul>
{products.map(product => (
<li key={product.id}>
{product.name} - {product.price}
</li>
))}
</ul>
</div>
)
}
```
---
### API Routes (Route Handlers)
Bouw je eigen API in Next.js:
**Folder structuur:**
```
app/
└── api/
└── products/
└── route.ts → /api/products
```
**GET request (`app/api/products/route.ts`):**
```typescript
import { NextResponse } from 'next/server'
const products = [
{ id: 1, name: 'Laptop', price: 999 },
{ id: 2, name: 'Phone', price: 699 },
]
export async function GET() {
return NextResponse.json(products)
}
```
**POST request:**
```typescript
export async function POST(request: Request) {
const body = await request.json()
const newProduct = {
id: Date.now(),
name: body.name,
price: body.price,
}
products.push(newProduct)
return NextResponse.json(newProduct, { status: 201 })
}
```
---
### 'use client' Directive
Wanneer je interactie nodig hebt:
```tsx
'use client' // MOET bovenaan!
import { useState } from 'react'
export function Counter() {
const [count, setCount] = useState(0)
return (
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
)
}
```
---
### React Query (TanStack Query)
**Waarom React Query?**
- Automatische caching
- Loading en error states
- Refetching (focus, interval)
- Optimistic updates
**Installatie:**
```bash
npm install @tanstack/react-query
```
**Setup Provider (`app/providers.tsx`):**
```tsx
'use client'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { useState } from 'react'
export function Providers({ children }: { children: React.ReactNode }) {
const [queryClient] = useState(() => new QueryClient())
return (
<QueryClientProvider client={queryClient}>
{children}
</QueryClientProvider>
)
}
```
**In Layout:**
```tsx
import { Providers } from './providers'
export default function RootLayout({ children }) {
return (
<html>
<body>
<Providers>{children}</Providers>
</body>
</html>
)
}
```
---
### useQuery - Data Ophalen
```tsx
'use client'
import { useQuery } from '@tanstack/react-query'
interface Product {
id: number
name: string
price: number
}
async function fetchProducts(): Promise<Product[]> {
const res = await fetch('/api/products')
return res.json()
}
export function ProductList() {
const { data, isLoading, error } = useQuery({
queryKey: ['products'],
queryFn: fetchProducts,
})
if (isLoading) return <div>Laden...</div>
if (error) return <div>Error: {error.message}</div>
return (
<ul>
{data?.map(product => (
<li key={product.id}>{product.name}</li>
))}
</ul>
)
}
```
---
### useMutation - Data Wijzigen
```tsx
'use client'
import { useMutation, useQueryClient } from '@tanstack/react-query'
interface NewProduct {
name: string
price: number
}
async function createProduct(product: NewProduct) {
const res = await fetch('/api/products', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(product),
})
return res.json()
}
export function AddProductForm() {
const queryClient = useQueryClient()
const mutation = useMutation({
mutationFn: createProduct,
onSuccess: () => {
// Invalidate and refetch
queryClient.invalidateQueries({ queryKey: ['products'] })
},
})
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault()
mutation.mutate({ name: 'New Product', price: 99 })
}
return (
<form onSubmit={handleSubmit}>
<button type="submit" disabled={mutation.isPending}>
{mutation.isPending ? 'Toevoegen...' : 'Voeg toe'}
</button>
</form>
)
}
```
---
### Combineren: Server + Client
```tsx
// app/products/page.tsx (Server Component)
import { ProductList } from './product-list'
import { AddProductForm } from './add-product-form'
export default function ProductsPage() {
return (
<div>
<h1>Producten</h1>
<AddProductForm /> {/* Client Component */}
<ProductList /> {/* Client Component */}
</div>
)
}
```
---
## Tools
- Next.js 14
- React Query (TanStack Query)
- TypeScript
- OpenCode/WebStorm
---
## Lesopdracht (2 uur)
### Bouw CRUD App met API Routes
**Deel 1: API Routes (40 min)**
1. Maak `app/api/products/route.ts`
2. Implementeer GET (alle producten)
3. Implementeer POST (product toevoegen)
4. Test met browser/Postman: `/api/products`
**Deel 2: React Query Setup (20 min)**
1. Installeer `@tanstack/react-query`
2. Maak `app/providers.tsx`
3. Wrap app in `QueryClientProvider`
**Deel 3: Data Tonen met useQuery (30 min)**
1. Maak `ProductList` Client Component
2. Gebruik `useQuery` om data te fetchen
3. Toon loading state
4. Toon error state
5. Render product lijst
**Deel 4: Data Toevoegen met useMutation (30 min)**
1. Maak `AddProductForm` Client Component
2. Gebruik `useMutation` voor POST
3. Invalidate query na success
4. Toon "Adding..." state
### Deliverable
- Werkende API routes (GET, POST)
- ProductList met useQuery
- AddProductForm met useMutation
- Loading en error states
---
## Huiswerk (2 uur)
### Volledige CRUD Interface
**Deel 1: PUT en DELETE Routes (45 min)**
1. Maak `app/api/products/[id]/route.ts`
2. Implementeer PUT (update product)
3. Implementeer DELETE (verwijder product)
4. Test beide endpoints
**Deel 2: Update Functionaliteit (45 min)**
1. Maak edit form in ProductList
2. Gebruik useMutation voor PUT
3. Inline editing OF modal
4. Invalidate query na success
**Deel 3: Delete Functionaliteit (30 min)**
1. Voeg delete button toe per product
2. Gebruik useMutation voor DELETE
3. Voeg confirmation dialog toe
4. Invalidate query na success
**Bonus:** Optimistic Updates
- Product direct uit UI verwijderen
- Rollback als server faalt
### Deliverable
- Complete CRUD API (GET, POST, PUT, DELETE)
- UI voor alle operaties
- Error handling
- Optimistic updates (bonus)
---
## Leerdoelen
Na deze les kan de student:
- Uitleggen wanneer Server vs Client Components
- De 'use client' directive correct gebruiken
- Data fetchen in Server Components met async/await
- API routes maken met Route Handlers
- GET en POST requests implementeren
- React Query installeren en configureren
- useQuery gebruiken voor data fetching
- useMutation gebruiken voor data mutations
- Loading en error states afhandelen
- Query invalidation toepassen