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

8.0 KiB

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:

// 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:

// 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):

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:

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:

'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:

npm install @tanstack/react-query

Setup Provider (app/providers.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:

import { Providers } from './providers'

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <Providers>{children}</Providers>
      </body>
    </html>
  )
}

useQuery - Data Ophalen

'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

'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

// 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