Wat is localStorage?
localStorage is een ingebouwde browser-feature die strings opslaat die bewaard blijven na een refresh of een herstart van de browser.
- Gebruikersnaam onthouden
- Dark mode voorkeur
- Todo-lijst die niet verdwijnt bij refresh
- Login-token bewaren (voor demo's — voor productie liever HTTP-only cookies)
Belangrijk om te weten:
- Strings only — voor objecten gebruik je
JSON.stringify() - Per domein — elke site heeft zijn eigen localStorage
- ±5MB ruimte — meer dan genoeg voor normale data
- Niet veilig voor gevoelige data — gebruiker kan het lezen via devtools
Lezen & schrijven — de basis
Opslaan
localStorage.setItem('username', 'Anna')
Lezen
const name = localStorage.getItem('username')
console.log(name) // 'Anna' (of null als er niks staat)
Verwijderen
localStorage.removeItem('username')
// Of alles wissen:
localStorage.clear()
Let op: getItem() geeft null terug als de key niet bestaat. Houd daar rekening mee met een fallback:
const name = localStorage.getItem('username') || 'Gast'
Objecten & arrays opslaan
localStorage slaat alleen strings op. Voor objecten en arrays gebruik je JSON.stringify() (object → string) en JSON.parse() (string → object).
Opslaan
const user = { name: 'Anna', age: 25 }
localStorage.setItem('user', JSON.stringify(user))
const todos = ['Boodschappen', 'Vue leren']
localStorage.setItem('todos', JSON.stringify(todos))
Lezen
const stored = localStorage.getItem('user')
const user = stored ? JSON.parse(stored) : null
console.log(user.name) // 'Anna'
const todosString = localStorage.getItem('todos')
const todos = todosString ? JSON.parse(todosString) : []
Beginnersfout:
localStorage.setItem('user', user) // ❌
// Opgeslagen als de string "[object Object]"
Altijd JSON.stringify voor objecten en arrays.
Patroon in Vue — lezen bij start, schrijven bij wijziging
In Vue kombineer je localStorage met refs op twee plekken:
- Bij setup: lees de opgeslagen waarde en zet hem in de ref
- Bij wijziging: gebruik een
watchdie naar localStorage schrijft
<script setup>
import { ref, watch } from 'vue'
// 1. Lees bij start (of gebruik default)
const username = ref(localStorage.getItem('username') || '')
// 2. Schrijf bij elke wijziging
watch(username, (newName) => {
localStorage.setItem('username', newName)
})
</script>
<template>
<input v-model="username" />
<p>Hallo, {{ username || 'Gast' }}!</p>
</template>
Wat heb je nu? Typ in het inputveld → de waarde wordt automatisch opgeslagen. Refresh de pagina → de waarde staat er nog. Pure two-way sync tussen je ref en de browser-storage.
Compleet voorbeeld — Todo-app
Een todo-lijst die persistent is. Items blijven staan na een refresh.
<script setup>
import { ref, watch } from 'vue'
// Lees bij start (parse JSON)
const stored = localStorage.getItem('todos')
const todos = ref(stored ? JSON.parse(stored) : [])
const input = ref('')
const addTodo = () => {
if (input.value.trim()) {
todos.value.push({
id: Date.now(),
text: input.value,
done: false
})
input.value = ''
}
}
const removeTodo = (id) => {
todos.value = todos.value.filter(t => t.id !== id)
}
// Sync naar localStorage bij elke wijziging (deep voor object-mutaties)
watch(todos, (newTodos) => {
localStorage.setItem('todos', JSON.stringify(newTodos))
}, { deep: true })
</script>
<template>
<form @submit.prevent="addTodo">
<input v-model="input" placeholder="Nieuwe taak..." />
<button type="submit">Voeg toe</button>
</form>
<ul>
<li v-for="todo in todos" :key="todo.id">
<input v-model="todo.done" type="checkbox" />
<span :class="{ done: todo.done }">{{ todo.text }}</span>
<button @click="removeTodo(todo.id)">×</button>
</li>
</ul>
</template>
<style scoped>
.done { text-decoration: line-through; color: #999; }
</style>
Let op { deep: true }: zonder dit detecteert de watcher alleen als todos.value wordt vervangen — niet als je binnen een todo de done aanvinkt. Met deep kijkt hij ook naar geneste wijzigingen.
Als composable — herbruikbaar
Doe je dit patroon vaker? Trek het uit naar een composable:
// src/composables/useLocalStorage.js
import { ref, watch } from 'vue'
export function useLocalStorage(key, defaultValue) {
const stored = localStorage.getItem(key)
const initial = stored !== null ? JSON.parse(stored) : defaultValue
const value = ref(initial)
watch(value, (newVal) => {
localStorage.setItem(key, JSON.stringify(newVal))
}, { deep: true })
return value
}
Gebruik
<script setup>
import { useLocalStorage } from '@/composables/useLocalStorage'
const username = useLocalStorage('username', '')
const todos = useLocalStorage('todos', [])
const isDark = useLocalStorage('dark-mode', false)
</script>
Eén regel per "ding dat persistent moet zijn." Of gebruik useLocalStorage uit VueUse — dezelfde API, al af.
Belangrijke Regels
- Alleen strings — gebruik
JSON.stringifyvoor objecten/arrays getItemkannullgeven — altijd een fallback- Lezen bij setup (in de ref initialisatie), schrijven via watch
- Voor objecten/arrays:
{ deep: true }in de watcher - Niet voor gevoelige data — gebruiker kan het lezen via devtools
Veelgemaakte Fouten
Fout — object opslaan zonder stringify:
const user = { name: 'Anna' }
localStorage.setItem('user', user) // ❌
console.log(localStorage.getItem('user')) // "[object Object]"
Goed:
localStorage.setItem('user', JSON.stringify(user))
const stored = JSON.parse(localStorage.getItem('user'))
Fout — null niet afvangen bij parse:
const user = JSON.parse(localStorage.getItem('user'))
// ❌ als de key niet bestaat: JSON.parse(null) → null (toevallig OK)
// Maar bij andere ongeldige strings → SyntaxError
Goed — check eerst:
const stored = localStorage.getItem('user')
const user = stored ? JSON.parse(stored) : null
Fout — vergeten deep: true voor object-refs:
const todos = ref([])
watch(todos, (val) => {
localStorage.setItem('todos', JSON.stringify(val))
})
// ❌ todos.value.push(...) wordt NIET gedetecteerd
Goed:
watch(todos, (val) => {
localStorage.setItem('todos', JSON.stringify(val))
}, { deep: true }) // ✅