Skip to content

PersonForm, Routing, and Views

FamilyTree needs a way to add and edit people. PersonForm is a shared component used by both AddPersonView and EditPersonView — it emits the form data, and the parent view decides what to do with it.

src/components/PersonForm/PersonForm.vue
<script setup lang="ts">
import { ref } from 'vue'
import type { Person, PersonFormData } from '../../types'
const props = defineProps<{
initial?: Partial<Person>
submitLabel?: string
}>()
const emit = defineEmits<{
submit: [data: PersonFormData]
cancel: []
}>()
const name = ref(props.initial?.name ?? '')
const birthYear = ref<number | ''>(props.initial?.birthYear ?? '')
const deathYear = ref<number | ''>(props.initial?.deathYear ?? '')
const bio = ref(props.initial?.bio ?? '')
const error = ref('')
function handleSubmit() {
if (!name.value.trim()) { error.value = 'Name is required.'; return }
error.value = ''
emit('submit', {
name: name.value.trim(),
birthYear: birthYear.value !== '' ? Number(birthYear.value) : undefined,
deathYear: deathYear.value !== '' ? Number(deathYear.value) : undefined,
bio: bio.value.trim() || undefined,
parentIds: props.initial?.parentIds ?? [],
stepParentIds: props.initial?.stepParentIds ?? [],
spouses: props.initial?.spouses ?? [],
siblingIds: props.initial?.siblingIds ?? [],
})
}
</script>
<template>
<form @submit.prevent="handleSubmit">
<p v-if="error" class="form-error">{{ error }}</p>
<div>
<label>Name *</label>
<input v-model="name" type="text" placeholder="Full name" required />
</div>
<div>
<label>Birth year</label>
<input v-model="birthYear" type="number" placeholder="e.g. 1952" />
</div>
<div>
<label>Death year</label>
<input v-model="deathYear" type="number" placeholder="Leave blank if living" />
</div>
<div>
<label>Bio</label>
<textarea v-model="bio" placeholder="A few notes (optional)" />
</div>
<div>
<button type="submit">{{ submitLabel ?? 'Save' }}</button>
<button type="button" @click="emit('cancel')">Cancel</button>
</div>
</form>
</template>
src/views/AddPersonView.vue
<script setup lang="ts">
import { useRouter } from 'vue-router'
import { useFamilyStore } from '../stores/familyStore'
import PersonForm from '../components/PersonForm/PersonForm.vue'
const store = useFamilyStore()
const router = useRouter()
function handleSubmit(data) {
const id = store.addPerson(data)
router.push({ name: 'person', params: { id } })
}
</script>
<template>
<h1>Add Person</h1>
<PersonForm
submit-label="Add"
@submit="handleSubmit"
@cancel="router.push({ name: 'home' })"
/>
</template>
src/views/EditPersonView.vue
<script setup lang="ts">
import { computed } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useFamilyStore } from '../stores/familyStore'
import { storeToRefs } from 'pinia'
import PersonForm from '../components/PersonForm/PersonForm.vue'
const route = useRoute()
const router = useRouter()
const store = useFamilyStore()
const { people } = storeToRefs(store)
const id = route.params.id as string
const person = computed(() => people.value.find(p => p.id === id))
function handleSubmit(data) {
store.updatePerson(id, data)
router.push({ name: 'person', params: { id } })
}
</script>
<template>
<div v-if="person">
<h1>Edit {{ person.name }}</h1>
<PersonForm
:initial="person"
submit-label="Save"
@submit="handleSubmit"
@cancel="router.push({ name: 'person', params: { id } })"
/>
</div>
<p v-else>Person not found.</p>
</template>

PersonForm uses @submit.prevent to handle the native form submit event and calls handleSubmit. The validation (name required) runs inside handleSubmit before emitting. The parent view (AddPersonView or EditPersonView) handles what happens after the submit event — navigation to the appropriate route.

EditPersonView passes :initial="person" to pre-populate the form. PersonForm reads props.initial to set initial ref values.

  1. Build PersonForm.vue, AddPersonView.vue, and EditPersonView.vue.
  2. Wire up the router with all four routes.
  3. Test the full flow: navigate to /add, add a person, confirm you’re redirected to their detail page.
  4. Navigate to /edit/:id for a person. Confirm the form is pre-populated with their data.
  • PersonForm emits form data and is reused by both Add and Edit views.
  • @submit.prevent on the <form> tag prevents page reload; the handler validates then emits.
  • Add view: store.addPerson(data) then navigate to the new person’s detail page.
  • Edit view: read route.params.id, find the person, pass as :initial, then store.updatePerson on submit.