Personal Catalogue: React, Express & MongoDB — Chapter 4, Exercise 1 ===================================================================== TASK Build the full AddItemForm component with FIELD_CONFIG covering all four types, wire it into App.jsx, and create one real item of each type through the actual UI, confirming each one via GET /api/items. SOLUTION src/fieldConfig.js: export const ITEM_TYPES = ['Book', 'Cd', 'Dvd', 'Bluray']; export const FIELD_CONFIG = { Book: [ { name: 'author', label: 'Author', required: true }, { name: 'isbn', label: 'ISBN' }, { name: 'publisher', label: 'Publisher' }, { name: 'tags', label: 'Tags (comma-separated)', isArray: true }, ], Cd: [ { name: 'artist', label: 'Artist', required: true }, { name: 'tracklist', label: 'Tracklist (comma-separated)', isArray: true }, ], Dvd: [ { name: 'director', label: 'Director' }, { name: 'runtimeMinutes', label: 'Runtime (minutes)', type: 'number' }, { name: 'regionCode', label: 'Region Code' }, ], Bluray: [ { name: 'director', label: 'Director' }, { name: 'runtimeMinutes', label: 'Runtime (minutes)', type: 'number' }, { name: 'resolution', label: 'Resolution', options: ['1080p', '4K'] }, ], }; src/components/AddItemForm.jsx (full component): import { useState } from 'react'; import { ITEM_TYPES, FIELD_CONFIG } from '../fieldConfig'; export default function AddItemForm({ onCreated }) { const [itemType, setItemType] = useState('Book'); const [shared, setShared] = useState({ title: '', releaseYear: '', notes: '' }); const [typeFields, setTypeFields] = useState({}); function handleTypeChange(e) { setItemType(e.target.value); setTypeFields({}); } function handleTypeFieldChange(name, value) { setTypeFields((prev) => ({ ...prev, [name]: value })); } async function handleSubmit(e) { e.preventDefault(); const payload = { itemType, title: shared.title, releaseYear: shared.releaseYear ? Number(shared.releaseYear) : undefined, notes: shared.notes || undefined, ...typeFields, }; for (const field of FIELD_CONFIG[itemType]) { if (field.isArray && typeof payload[field.name] === 'string') { payload[field.name] = payload[field.name].split(',').map((s) => s.trim()).filter(Boolean); } } const res = await fetch('http://localhost:4000/api/items', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }); if (!res.ok) { const { error } = await res.json(); alert(error); return; } const created = await res.json(); onCreated(created); setShared({ title: '', releaseYear: '', notes: '' }); setTypeFields({}); } return (
{FIELD_CONFIG[itemType].map((field) => ( ))}
); } src/App.jsx: import AddItemForm from './components/AddItemForm'; export default function App() { return (

Personal Catalogue

console.log('Created:', item)} />
); } Testing all four types through the real UI, one at a time: Book: title "Clean Code", author "Robert C. Martin", tags "Programming, Software" Cd: title "OK Computer", artist "Radiohead" Dvd: title "Seven Samurai", director "Akira Kurosawa", runtimeMinutes 207 Bluray: title "Blade Runner 2049", director "Denis Villeneuve", resolution "4K" Run with: npx nodemon server.js (backend) and npm run dev (frontend, in catalogue-web), then submit each item through the browser form Output: each submission logs "Created: {...}" in the browser console with a real _id; a follow-up curl http://localhost:4000/api/items returns all four documents, each carrying only the fields its own type actually has. WHY THIS WORKS AS AN ANSWER ---------------------------- It builds the form exactly as the chapter's own field-configuration approach describes (one component, one config object, no per-type JSX duplication), wires it into the app with a real onCreated callback, and proves all four types genuinely work by creating and verifying one real document of each, rather than testing only one type and assuming the others behave the same.