fix: add lesson 6
This commit is contained in:
306
Les06-NextJS-QuickPoll-Part2/Les06-Docenttekst.md
Normal file
306
Les06-NextJS-QuickPoll-Part2/Les06-Docenttekst.md
Normal file
@@ -0,0 +1,306 @@
|
||||
# Les 6: Next.js — QuickPoll Compleet - Docenttekst
|
||||
|
||||
**NOVI Hogeschool | Instructeur: Tim | Duur: 180 minuten**
|
||||
|
||||
---
|
||||
|
||||
## VOORBEREIDING
|
||||
|
||||
**Checklist (30 minuten voor les):**
|
||||
- [ ] Demo project klaarzetten: `quickpoll-demo` uitpakken, `npm install`
|
||||
- [ ] `git checkout stap-0` → startpunt
|
||||
- [ ] Test: `git checkout bonus` → volledige app draait
|
||||
- [ ] Terug naar `git checkout stap-0`
|
||||
- [ ] Cursor openen, terminal font minimaal 16pt
|
||||
- [ ] Browser met `localhost:3000` open
|
||||
- [ ] **Scherm delen:** Teams → deel alleen het Cursor-venster
|
||||
- [ ] Browser/notities op apart Space (voor stiekem opzoeken)
|
||||
- [ ] Losse stap-zipjes als backup
|
||||
|
||||
**Tempo-indicatie:**
|
||||
| Blok | Tijd | Inhoud |
|
||||
|------|------|--------|
|
||||
| Stap 0-3 (recap) | 0:00-0:40 | Project, types, layout, homepage, GET route |
|
||||
| Stap 4 | 0:40-1:00 | POST vote route |
|
||||
| Stap 5 | 1:00-1:15 | Poll detail pagina |
|
||||
| **PAUZE** | **1:15-1:30** | |
|
||||
| Stap 6 | 1:30-2:10 | VoteForm (logica + UI) |
|
||||
| Stap 7 | 2:10-2:30 | Loading, Error, Not-Found |
|
||||
| Stap 8 | 2:30-2:45 | Middleware |
|
||||
| Afsluiting | 2:45-3:00 | Huiswerk, vragen |
|
||||
|
||||
---
|
||||
|
||||
## STAP 0-3: RECAP (0:00-0:40)
|
||||
|
||||
*Dit gaat snel — studenten kennen het al. Doel: iedereen op hetzelfde punt krijgen.*
|
||||
|
||||
### Slide 1: Titel + Plan (0:00-0:02)
|
||||
|
||||
> "Welkom terug! Vorige les hebben jullie kennis gemaakt met Next.js. Vandaag bouwen we de hele QuickPoll app van scratch tot werkend."
|
||||
|
||||
> "Ik code, jullie volgen mee. Stap 0-3 kennen jullie — dat gaat snel. Stap 4-8 is nieuw, daar nemen we de tijd voor."
|
||||
|
||||
---
|
||||
|
||||
### Slide 2: Stap 0 — Project Aanmaken (0:02-0:07)
|
||||
|
||||
*Tim opent terminal.*
|
||||
|
||||
> "Stap 0: `npx create-next-app@latest quickpoll`. TypeScript, Tailwind, App Router — alles yes."
|
||||
|
||||
*Tim wacht tot iedereen `npm run dev` draait.*
|
||||
|
||||
> "Zie je de Next.js standaard pagina? Door."
|
||||
|
||||
**⚡ Snelheid:** Studenten die dit al hebben kunnen alvast door naar stap 1.
|
||||
|
||||
---
|
||||
|
||||
### Slide 3: Stap 1 — Types & Data (0:07-0:15)
|
||||
|
||||
> "Twee bestanden: `src/types/index.ts` met de Poll interface, en `src/lib/data.ts` met onze in-memory database."
|
||||
|
||||
*Tim typt de interface.*
|
||||
|
||||
> "Een poll: id, question, options, votes. De votes array loopt parallel met options — index 0 van votes hoort bij index 0 van options."
|
||||
|
||||
*Tim typt data.ts.*
|
||||
|
||||
> "Drie helper functies: getPolls, getPollById, votePoll. Dit is onze 'database' — later vervangen we dit door Supabase."
|
||||
|
||||
*Checkpoint: "Beide bestanden staan er? Door."*
|
||||
|
||||
---
|
||||
|
||||
### Slide 4: Stap 2 — Layout (0:15-0:22)
|
||||
|
||||
> "De layout wrapt elke pagina. Navbar, main content, footer. Verandert nooit."
|
||||
|
||||
*Tim vervangt layout.tsx.*
|
||||
|
||||
> "`Link` is Next.js client-side navigatie — geen page reload. `metadata` is je SEO titel."
|
||||
|
||||
**⚡ Tip:** Niet te lang stilstaan, dit kennen ze.
|
||||
|
||||
---
|
||||
|
||||
### Slide 5: Stap 2 vervolg — Homepage (0:22-0:30)
|
||||
|
||||
> "De homepage: een Server Component die alle polls ophaalt en cards rendert."
|
||||
|
||||
*Tim vervangt page.tsx.*
|
||||
|
||||
> "Geen `'use client'`, geen useEffect, geen loading state. Server Component haalt data direct op. Dat is het voordeel."
|
||||
|
||||
> "De `.map()` rendert een card per poll. `.reduce()` telt totaal stemmen."
|
||||
|
||||
*Checkpoint: "3 cards op localhost:3000? Mooi."*
|
||||
|
||||
---
|
||||
|
||||
### Slide 6: Stap 3 — GET API Route (0:30-0:40)
|
||||
|
||||
> "Onze eerste API route. De folder-structuur IS je URL: `api/polls/[id]/route.ts` wordt `/api/polls/1`."
|
||||
|
||||
*Tim maakt het bestand.*
|
||||
|
||||
> "Dynamic route: `[id]` met vierkante haakjes. De waarde uit de URL wordt de `id` parameter."
|
||||
|
||||
> "`await params` — Next.js 15 ding. Params zijn een Promise. Vergeet de await niet."
|
||||
|
||||
*Tim test in browser: localhost:3000/api/polls/1.*
|
||||
|
||||
> "JSON! Test ook `/api/polls/999` — 404. Mooi, recap klaar. Nu het nieuwe werk."
|
||||
|
||||
---
|
||||
|
||||
## STAP 4-5: NIEUW MATERIAAL DEEL 1 (0:40-1:15)
|
||||
|
||||
### Slide 7: Stap 4 — POST Vote Route (0:40-1:00)
|
||||
|
||||
**💡 Theorie-moment: POST vs GET**
|
||||
|
||||
> "GET leest data, POST wijzigt data. Het patroon voor elke POST route: params, body, validatie, actie, response. Vijf stappen, altijd."
|
||||
|
||||
*Tim maakt src/app/api/polls/[id]/vote/route.ts.*
|
||||
|
||||
> "Nieuwe folder: `vote/` in de `[id]/` folder. Zo krijg je `/api/polls/1/vote`."
|
||||
|
||||
*Tim typt de code langzaam.*
|
||||
|
||||
> "`request.json()` leest de body — wat de client stuurt. Hier: welke optie."
|
||||
|
||||
> "Twee error checks: 400 als data ongeldig, 404 als poll niet bestaat. Altijd beide."
|
||||
|
||||
*Tim opent browser console, test met fetch.*
|
||||
|
||||
> "Plak dit in je console. Zie je? Votes veranderd in de JSON. Onze stem-API werkt."
|
||||
|
||||
*Checkpoint: "Heeft iedereen JSON in de console? Mooi."*
|
||||
|
||||
---
|
||||
|
||||
### Slide 8: Stap 5 — Poll Detail Pagina (1:00-1:15)
|
||||
|
||||
**💡 Theorie-moment: generateMetadata & notFound**
|
||||
|
||||
> "`generateMetadata` — speciale Next.js functie voor dynamische SEO. Elke poll krijgt z'n eigen browser tab titel."
|
||||
|
||||
> "`notFound()` — roep je aan als data niet bestaat. Next.js toont automatisch de 404 pagina."
|
||||
|
||||
*Tim maakt src/app/poll/[id]/page.tsx.*
|
||||
|
||||
> "Dit is de combinatie: Server Component haalt data, rendert Client Component (`VoteForm`). Server = data, client = interactie."
|
||||
|
||||
> "We importeren VoteForm maar die bestaat nog niet — dat bouwen we na de pauze."
|
||||
|
||||
*Checkpoint: "/poll/1 geeft een error — dat klopt, VoteForm bestaat nog niet."*
|
||||
|
||||
---
|
||||
|
||||
## PAUZE (1:15-1:30)
|
||||
|
||||
### Slide 9: Pauze
|
||||
|
||||
> "Pauze! 15 minuten. Stap 0-5 staan er. Na de pauze bouwen we het interactieve hart: de VoteForm."
|
||||
|
||||
*Tim loopt rond en helpt studenten die achterlopen.*
|
||||
|
||||
---
|
||||
|
||||
## STAP 6: VOTEFORM (1:30-2:10)
|
||||
|
||||
*Dit is het langste en lastigste stuk. Neem de tijd.*
|
||||
|
||||
### Slide 10: Stap 6 deel 1 — Logica (1:30-1:50)
|
||||
|
||||
**💡 Theorie-moment: Client Components & useState**
|
||||
|
||||
> "`'use client'` bovenaan — dit component draait in de browser. Alles met useState, onClick, of interactiviteit MOET een Client Component zijn."
|
||||
|
||||
*Tim maakt src/components/VoteForm.tsx.*
|
||||
|
||||
> "Vier states."
|
||||
|
||||
*Tim typt elke state en legt uit.*
|
||||
|
||||
> "`selectedOption: number | null` — null want er kan nog niks geselecteerd zijn."
|
||||
> "`hasVoted: boolean` — toggle na stemmen."
|
||||
> "`isSubmitting: boolean` — voorkom dubbel klikken."
|
||||
> "`currentPoll: Poll` — de actuele data, updated na stemmen."
|
||||
|
||||
*Checkpoint: "Heeft iedereen de vier useState regels?"*
|
||||
|
||||
*Tim typt handleVote.*
|
||||
|
||||
> "De handleVote functie: dezelfde fetch als in de console bij stap 4. POST naar de API, update state als het lukt."
|
||||
|
||||
*Checkpoint: "Tot hier mee?"*
|
||||
|
||||
---
|
||||
|
||||
### Slide 11: Stap 6 deel 2 — UI (1:50-2:10)
|
||||
|
||||
*Tim typt de JSX rustig, stopt na elk blok.*
|
||||
|
||||
> "De UI heeft twee toestanden. Vóór stemmen: selecteer een optie. Na stemmen: percentage bars."
|
||||
|
||||
*Tim typt de map over options.*
|
||||
|
||||
> "Elke optie is een button. onClick selecteert de optie — maar alleen als je nog niet gestemd hebt."
|
||||
|
||||
> "De conditional classes: `isSelected ? 'border-purple-500' : 'border-gray-200'`. Ternary operators in className — zo style je conditioneel in Tailwind."
|
||||
|
||||
*Tim typt de percentage bar.*
|
||||
|
||||
> "De percentage bar: een div met `absolute inset-0`, breedte is percentage, `transition-all duration-500` animeert. Pure CSS."
|
||||
|
||||
*Tim typt stem-knop en bedankt-bericht.*
|
||||
|
||||
> "Stem-knop verdwijnt na stemmen. Bedankt-bericht verschijnt. Allebei conditional rendering met `hasVoted`."
|
||||
|
||||
*Tim test: localhost:3000/poll/1 → klik optie → stem.*
|
||||
|
||||
> "Werkt het? Klik een optie, klik Stem, zie de animatie!"
|
||||
|
||||
*Tim loopt rond en helpt. **Neem hier 10-15 min extra als nodig.** Dit is het lastigste stuk.*
|
||||
|
||||
**Waar lopen studenten vast?**
|
||||
- `"use client"` vergeten → crash
|
||||
- `selectedOption` niet nullable (`number` i.p.v. `number | null`)
|
||||
- Fetch URL zonder backticks (template literal)
|
||||
- Tailwind class typos
|
||||
- Vergeten om het bestand op te slaan
|
||||
|
||||
**Snelle fix:** "Check je terminal — TypeScript error? Heb je `'use client'` bovenaan?"
|
||||
|
||||
---
|
||||
|
||||
## STAP 7-8: FINISHING TOUCHES (2:10-2:45)
|
||||
|
||||
### Slide 12: Stap 7 — Loading, Error & Not-Found (2:10-2:30)
|
||||
|
||||
**💡 Theorie-moment: Speciale bestanden**
|
||||
|
||||
> "In gewoon React bouw je dit zelf. In Next.js: maak een bestand aan en het werkt."
|
||||
|
||||
*Tim maakt loading.tsx.*
|
||||
|
||||
> "Skeleton loading. `animate-pulse` — Tailwind class. Vijf regels, professioneel resultaat."
|
||||
|
||||
*Tim maakt error.tsx.*
|
||||
|
||||
> "Error boundary. **`'use client'` is verplicht!** Error boundaries draaien altijd in de browser. `reset` herlaadt het gefaalde component."
|
||||
|
||||
*Tim maakt not-found.tsx.*
|
||||
|
||||
> "404 pagina. Test: `/poll/999`. Daar is 'm. Werkt door `notFound()` in de poll pagina."
|
||||
|
||||
*Checkpoint: "Alle drie aangemaakt en werkend?"*
|
||||
|
||||
---
|
||||
|
||||
### Slide 13: Stap 8 — Middleware (2:30-2:45)
|
||||
|
||||
**💡 Theorie-moment: Middleware**
|
||||
|
||||
> "Middleware is een portier. Elke request passeert hier eerst."
|
||||
|
||||
*Tim maakt src/middleware.ts.*
|
||||
|
||||
> "Let op de locatie: `src/middleware.ts`. Niet in `app/`, niet in een subfolder. Dit is het enige bestand waar de locatie uitmaakt."
|
||||
|
||||
> "De `matcher` bepaalt welke routes het raakt. API routes en poll pagina's — de homepage skipt het."
|
||||
|
||||
> "Open je terminal. Klik op een poll. Zie je `[GET] /poll/1`? Dat is middleware in actie."
|
||||
|
||||
> "Nu loggen we alleen. Bij Supabase wordt dit authenticatie: is de user ingelogd? Zo niet, redirect."
|
||||
|
||||
*Checkpoint: "Logs in je terminal? App compleet!"*
|
||||
|
||||
---
|
||||
|
||||
## AFSLUITING (2:45-3:00)
|
||||
|
||||
### Slide 14: Huiswerk & Afsluiting
|
||||
|
||||
> "Wat hebben we vandaag gedaan? De hele QuickPoll app van scratch: types, layout, homepage, API routes, detail pagina, VoteForm met animaties, loading states, middleware."
|
||||
|
||||
> "De Next.js flow: Route → Page → Client Component → API Route → Response. Dat is het hele framework in één zin."
|
||||
|
||||
> "Huiswerk: app niet af? Afmaken. Wel af? Probeer de bonus: een '/create' pagina met een form en POST naar `/api/polls`."
|
||||
|
||||
> "Volgende les: Tailwind CSS en shadcn/ui. Dan geven we QuickPoll een styling upgrade."
|
||||
|
||||
> "In één les van nul naar werkende app. Goed gedaan!"
|
||||
|
||||
---
|
||||
|
||||
## POST-SESSIE
|
||||
|
||||
**Na afloop checken:**
|
||||
- [ ] Hoeveel studenten hebben werkende VoteForm?
|
||||
- [ ] Was het tempo goed? (stap 0-3 snel genoeg, stap 4-8 genoeg tijd?)
|
||||
- [ ] Terugkerende problemen notieren voor v2
|
||||
- [ ] Was van-scratch beter dan doorgaan waar Les 5 stopte?
|
||||
Reference in New Issue
Block a user