Het patroon — drie stappen

Een formulier in Vue bouwen draait om drie dingen:

  1. Refs voor elke input-waarde
  2. v-model om elke input aan zijn ref te koppelen
  3. @submit.prevent op het <form> om te reageren op verzenden

Belangrijk: deze pagina gaat over het opbouwen van een formulier. De details van v-model op verschillende input-types (radio, checkbox, select) staan op de v-model pagina.

Simpel formulier

<script setup>
import { ref } from 'vue'

const name = ref('')
const email = ref('')

const handleSubmit = () => {
  console.log('Verstuurd:', name.value, email.value)
}
</script>

<template>
  <form @submit.prevent="handleSubmit">
    <label>
      Naam:
      <input v-model="name" type="text" />
    </label>

    <label>
      Email:
      <input v-model="email" type="email" />
    </label>

    <button type="submit">Versturen</button>
  </form>
</template>

Drie dingen om op te merken:

  1. @submit.prevent — voorkomt dat de pagina ververst
  2. De knop heeft type="submit" — anders triggert hij submit niet
  3. v-model regelt automatisch de two-way binding — geen @input nodig

Simpele validatie

Voor basis validatie heb je twee opties:

Optie 1: check in de submit-handler

<script setup>
import { ref } from 'vue'

const email = ref('')
const error = ref('')

const handleSubmit = () => {
  // Reset error
  error.value = ''

  // Check
  if (!email.value.includes('@')) {
    error.value = 'Vul een geldig e-mailadres in'
    return
  }

  // Alles goed — versturen
  console.log('Versturen:', email.value)
}
</script>

<template>
  <form @submit.prevent="handleSubmit">
    <input v-model="email" />
    <p v-if="error" style="color: red">{{ error }}</p>
    <button type="submit">Versturen</button>
  </form>
</template>

Optie 2: button disablen met computed

<script setup>
import { ref, computed } from 'vue'

const name = ref('')
const email = ref('')

const isValid = computed(() => {
  return name.value.length > 0 && email.value.includes('@')
})
</script>

<template>
  <form @submit.prevent="handleSubmit">
    <input v-model="name" placeholder="Naam" />
    <input v-model="email" placeholder="Email" />

    <button type="submit" :disabled="!isValid">
      Versturen
    </button>
  </form>
</template>

Welke optie? Voor beginners is optie 1 (check in de handler) het duidelijkst. Optie 2 (computed + disabled) is mooier maar abstracter. Beide werken prima.

Form resetten na verzenden

Wil je dat het formulier leeg is na verzenden? Zet de refs terug naar hun lege waarde.

<script setup>
import { ref } from 'vue'

const name = ref('')
const email = ref('')

const handleSubmit = () => {
  console.log('Versturen:', name.value, email.value)

  // Reset
  name.value = ''
  email.value = ''
}
</script>

Tip: Bij grotere formulieren wordt dat snel veel regels. Dan is het handiger om alle velden in één object te zetten — zie het volgende voorbeeld.

Compleet voorbeeld — registratieformulier

Alles bij elkaar: meerdere velden in één object, validatie, en reset na succes.

<script setup>
import { ref } from 'vue'

const form = ref({
  name: '',
  email: '',
  password: '',
  acceptTerms: false
})

const error = ref('')
const success = ref(false)

const handleSubmit = () => {
  error.value = ''
  success.value = false

  // Validatie
  if (form.value.name.length < 2) {
    error.value = 'Naam is te kort'
    return
  }
  if (!form.value.email.includes('@')) {
    error.value = 'Ongeldig email'
    return
  }
  if (form.value.password.length < 8) {
    error.value = 'Wachtwoord moet minimaal 8 tekens zijn'
    return
  }
  if (!form.value.acceptTerms) {
    error.value = 'Je moet akkoord gaan met de voorwaarden'
    return
  }

  // Verstuur (hier zou normaal een fetch komen)
  console.log('Registreren:', form.value)
  success.value = true

  // Reset
  form.value = {
    name: '',
    email: '',
    password: '',
    acceptTerms: false
  }
}
</script>

<template>
  <form @submit.prevent="handleSubmit">
    <label>
      Naam:
      <input v-model="form.name" type="text" />
    </label>

    <label>
      Email:
      <input v-model="form.email" type="email" />
    </label>

    <label>
      Wachtwoord:
      <input v-model="form.password" type="password" />
    </label>

    <label>
      <input v-model="form.acceptTerms" type="checkbox" />
      Ik ga akkoord met de voorwaarden
    </label>

    <p v-if="error" style="color: red">{{ error }}</p>
    <p v-if="success" style="color: green">Account aangemaakt! ✅</p>

    <button type="submit">Registreren</button>
  </form>
</template>

Het patroon: één form ref met alle velden, één handleSubmit die valideert + verstuurt + reset, en aparte refs voor error en success meldingen.

Belangrijke Regels

  • Altijd @submit.prevent op het <form> — anders refresht de pagina
  • Knop heeft type="submit" om de submit te triggeren
  • Gebruik v-model op elke input — niet @input handmatig
  • Meer dan 2-3 velden? Zet ze in één object i.p.v. losse refs
  • Validatie? Doe het in de submit-handler, return vroeg bij fouten

Veelgemaakte Fouten

Fout — geen .prevent:

<form @submit="handleSubmit">     <!-- ❌ pagina ververst -->

Goed:

<form @submit.prevent="handleSubmit">

Fout — knop zonder type="submit":

<form @submit.prevent="handleSubmit">
  <input v-model="name" />
  <button>Versturen</button>       <!-- ❌ default is geen submit -->
</form>

Goed:

<button type="submit">Versturen</button>

Of zet de actie op de knop: <button @click="handleSubmit"> (maar dan kan de gebruiker niet met Enter versturen).

Fout — v-model én aparte @input:

<input v-model="name" @input="name = $event.target.value" />
<!-- ❌ dubbel werk — v-model doet dit al -->

Goed:

<input v-model="name" />          <!-- ✅ -->