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.
PersonForm component
Section titled “PersonForm component”<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>AddPersonView
Section titled “AddPersonView”<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>EditPersonView
Section titled “EditPersonView”<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 stringconst 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>Key patterns
Section titled “Key patterns”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.
Exercise
Section titled “Exercise”- Build
PersonForm.vue,AddPersonView.vue, andEditPersonView.vue. - Wire up the router with all four routes.
- Test the full flow: navigate to
/add, add a person, confirm you’re redirected to their detail page. - Navigate to
/edit/:idfor a person. Confirm the form is pre-populated with their data.
PersonFormemits form data and is reused by both Add and Edit views.@submit.preventon 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, thenstore.updatePersonon submit.