# Les 6 — Lesopdracht
## QuickPoll Part 2 — Server Actions + polish
**Duur:** ~120 minuten klassikaal
---
## Doel
Aan het einde heeft QuickPoll:
- Server actions voor vote submission
- Een /create pagina voor nieuwe polls
- Loading + error states
- Optimistic updates
---
## Stap 1 — Server Action voor voting
`app/poll/[id]/actions.ts`:
```typescript
"use server";
import { polls } from "@/lib/data";
import { revalidatePath } from "next/cache";
export async function vote(pollId: string, optionId: string) {
const poll = polls.find(p => p.id === pollId);
if (!poll) throw new Error("Poll niet gevonden");
const option = poll.options.find(o => o.id === optionId);
if (!option) throw new Error("Optie niet gevonden");
option.votes += 1;
revalidatePath(`/poll/${pollId}`);
}
```
---
## Stap 2 — VoteForm met server action
Refactor `components/VoteForm.tsx` om `vote()` server action te gebruiken in plaats van useState:
```tsx
"use client";
import { vote } from "@/app/poll/[id]/actions";
import { useFormStatus } from "react-dom";
function VoteButton({ option }) {
const { pending } = useFormStatus();
return (
);
}
export function VoteForm({ poll }) {
return (
{poll.options.map(o => (
))}
);
}
```
---
## Stap 3 — /create pagina
`app/create/page.tsx`:
```tsx
import { createPoll } from "./actions";
export default function CreatePage() {
return (
Nieuwe poll
);
}
```
`app/create/actions.ts`:
```typescript
"use server";
import { polls } from "@/lib/data";
import { redirect } from "next/navigation";
import { revalidatePath } from "next/cache";
export async function createPoll(formData: FormData) {
const title = formData.get("title") as string;
const optionsText = formData.get("options") as string;
const options = optionsText.split("\n").map(t => t.trim()).filter(Boolean);
if (!title || options.length < 2) {
throw new Error("Vul titel + min 2 opties in");
}
const newPoll = {
id: String(polls.length + 1),
title,
options: options.map((text, i) => ({
id: String.fromCharCode(97 + i),
text,
votes: 0,
})),
};
polls.push(newPoll);
revalidatePath("/");
redirect(`/poll/${newPoll.id}`);
}
```
---
## Stap 4 — Loading + Error states
`app/poll/[id]/loading.tsx`:
```tsx
export default function Loading() {
return (
);
}
```
---
## Stap 5 — Polish
- Voeg op homepage een "Nieuwe poll maken" knop naar `/create`
- Voeg "Terug" link op detail-page
- Maak buttons mooier (rounded, transitions, focus rings)
---
## Eisen
- [ ] Voting werkt via Server Action (geen lokaal useState meer)
- [ ] /create pagina werkt en redirect na submit
- [ ] Loading state zichtbaar bij langzaam internet (DevTools throttling)
- [ ] Error state zichtbaar als je expres een error throw't
- [ ] Polish: alles voelt netjes af
---
## Bonus
- `useOptimistic` toevoegen aan VoteForm voor instant feedback
- Toast notification na vote (gebruik `sonner`)
- Animatie op vote-count change
---
## Klaar?
Push naar GitHub. Volgende les: vervang in-memory data door echte Supabase database.