# 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 } // Client Component - interactie 'use client' function AddToCartButton({ productId }) { const [added, setAdded] = useState(false) return } ``` --- ### 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 { const res = await fetch('https://api.example.com/products') return res.json() } export default async function ProductsPage() { const products = await getProducts() return (

Producten

) } ``` --- ### 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 ( ) } ``` --- ### 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 ( {children} ) } ``` **In Layout:** ```tsx import { Providers } from './providers' export default function RootLayout({ children }) { return ( {children} ) } ``` --- ### useQuery - Data Ophalen ```tsx 'use client' import { useQuery } from '@tanstack/react-query' interface Product { id: number name: string price: number } async function fetchProducts(): Promise { const res = await fetch('/api/products') return res.json() } export function ProductList() { const { data, isLoading, error } = useQuery({ queryKey: ['products'], queryFn: fetchProducts, }) if (isLoading) return
Laden...
if (error) return
Error: {error.message}
return ( ) } ``` --- ### 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 (
) } ``` --- ### 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 (

Producten

{/* Client Component */} {/* Client Component */}
) } ``` --- ## 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