Address Book Management
This example demonstrates how to build a complete address book system where users can save, edit, delete, and manage multiple addresses. Perfect for e-commerce checkouts, delivery apps, and service booking platforms.
Live Demo
Add addresses to your address book, set a default address, and manage your saved locations. Data is stored in your browser's localStorage for demonstration purposes.
No saved addresses
Add your first address to get started
How It Works
This example demonstrates a complete address book system with CRUD operations and local storage persistence.
- Address Storage: Addresses are stored in
localStoragefor demonstration. In production, you would store addresses in a backend database with user authentication. - Address Types: Users can categorise addresses as Home, Work, or Other with custom labels. This helps organise multiple addresses and provides quick selection at checkout.
- Default Address: One address can be marked as default, which is automatically pre-selected in checkout forms. Only one address can be default at a time.
- Component Parsing: The autocomplete response is parsed into structured components (street number, street name, city, county, postcode, country) for easy form population and validation.
- Edit Functionality: When editing, the autocomplete is reinitialised and existing address components are pre-populated in the form fields for easy modification.
- Practical Applications: This pattern is essential for e-commerce platforms, food delivery apps, service booking systems, and any application where users have multiple delivery or service addresses.
<script> import { PlacesAutocomplete } from 'places-autocomplete-js'; document.addEventListener('DOMContentLoaded', () => { let addresses = []; let autocomplete; let editingId = null; // Load addresses from localStorage function loadAddresses() { const stored = localStorage.getItem('pac-addresses'); if (stored) { addresses = JSON.parse(stored); renderAddressList(); } } // Save addresses to localStorage function saveAddresses() { localStorage.setItem('pac-addresses', JSON.stringify(addresses)); } // Parse address components from Google Places function parseAddressComponents(components) { const mapped = components.reduce((acc, component) => { const type = component.types[0]; acc[type] = component.longText; return acc; }, {}); return { streetNumber: mapped.street_number || '', street: mapped.route || '', city: mapped.postal_town || mapped.locality || '', county: mapped.administrative_area_level_2 || mapped.administrative_area_level_1 || '', postcode: mapped.postal_code || '', country: mapped.country || '' }; } // Initialise autocomplete function initAutocomplete() { const container = document.getElementById('address-autocomplete'); if (!container) return; if (autocomplete) { autocomplete.destroy(); } autocomplete = new PlacesAutocomplete({ containerId: 'address-autocomplete', googleMapsApiKey: 'YOUR_GOOGLE_MAPS_API_KEY', onResponse: (placeDetails) => { // Populate form with selected address const parsed = parseAddressComponents(placeDetails.addressComponents); document.getElementById('formatted-address').value = placeDetails.formattedAddress; document.getElementById('street-number').value = parsed.streetNumber; document.getElementById('street-name').value = parsed.street; document.getElementById('city').value = parsed.city; document.getElementById('county').value = parsed.county; document.getElementById('postcode').value = parsed.postcode; document.getElementById('country').value = parsed.country; // Store coordinates document.getElementById('coordinates').value = JSON.stringify(placeDetails.location); }, onError: (error) => { console.error('Autocomplete Error:', error.message || error); }, requestParams: { region: 'GB', includedRegionCodes: ['GB'] }, fetchFields: ['formattedAddress', 'addressComponents', 'location'], options: { placeholder: 'Search for your address...', clear_input: false } }); } // Open add address form function openAddForm() { editingId = null; document.getElementById('form-title').textContent = 'Add New Address'; document.getElementById('address-form').reset(); // Set default if no addresses exist if (addresses.length === 0) { document.getElementById('is-default').checked = true; } document.getElementById('modal').classList.remove('hidden'); setTimeout(() => initAutocomplete(), 100); } // Open edit address form function openEditForm(id) { editingId = id; const address = addresses.find(a => a.id === id); if (!address) return; document.getElementById('form-title').textContent = 'Edit Address'; document.getElementById('address-type').value = address.type; document.getElementById('custom-label').value = address.type === 'other' ? address.label : ''; document.getElementById('formatted-address').value = address.formattedAddress; document.getElementById('street-number').value = address.components.streetNumber; document.getElementById('street-name').value = address.components.street; document.getElementById('city').value = address.components.city; document.getElementById('county').value = address.components.county; document.getElementById('postcode').value = address.components.postcode; document.getElementById('country').value = address.components.country; document.getElementById('is-default').checked = address.isDefault; document.getElementById('coordinates').value = JSON.stringify(address.coordinates); document.getElementById('modal').classList.remove('hidden'); setTimeout(() => initAutocomplete(), 100); } // Save address (add or update) function saveAddress(event) { event.preventDefault(); const formData = { type: document.getElementById('address-type').value, customLabel: document.getElementById('custom-label').value, formattedAddress: document.getElementById('formatted-address').value, components: { streetNumber: document.getElementById('street-number').value, street: document.getElementById('street-name').value, city: document.getElementById('city').value, county: document.getElementById('county').value, postcode: document.getElementById('postcode').value, country: document.getElementById('country').value }, isDefault: document.getElementById('is-default').checked, coordinates: JSON.parse(document.getElementById('coordinates').value || 'null') }; if (!formData.formattedAddress) { alert('Please select an address'); return; } // Determine label const typeLabels = { home: 'Home', work: 'Work', other: 'Other' }; const label = formData.type === 'other' && formData.customLabel ? formData.customLabel : typeLabels[formData.type]; const addressData = { id: editingId || Date.now().toString(), label: label, type: formData.type, formattedAddress: formData.formattedAddress, components: formData.components, coordinates: formData.coordinates, isDefault: formData.isDefault, createdAt: editingId ? addresses.find(a => a.id === editingId).createdAt : new Date().toISOString(), lastUsed: new Date().toISOString() }; if (editingId) { // Update existing const index = addresses.findIndex(a => a.id === editingId); addresses[index] = addressData; } else { // Add new addresses.push(addressData); } // If set as default, remove default from others if (formData.isDefault) { addresses = addresses.map(a => ({ ...a, isDefault: a.id === addressData.id })); } saveAddresses(); closeModal(); renderAddressList(); } // Delete address function deleteAddress(id) { if (!confirm('Are you sure you want to delete this address?')) return; const wasDefault = addresses.find(a => a.id === id)?.isDefault; addresses = addresses.filter(a => a.id !== id); // Set first address as default if we deleted the default if (wasDefault && addresses.length > 0) { addresses[0].isDefault = true; } saveAddresses(); renderAddressList(); } // Set default address function setDefaultAddress(id) { addresses = addresses.map(a => ({ ...a, isDefault: a.id === id })); saveAddresses(); renderAddressList(); } // Close modal function closeModal() { document.getElementById('modal').classList.add('hidden'); if (autocomplete) { autocomplete.destroy(); } } // Render address list function renderAddressList() { const container = document.getElementById('address-list'); if (addresses.length === 0) { container.innerHTML = ` <div class="empty-state"> <p>No saved addresses</p> <p class="text-sm">Add your first address to get started</p> </div> `; return; } const icons = { home: '🏠', work: '💼', other: '👤' }; container.innerHTML = addresses.map(address => ` <div class="address-card"> <div class="address-header"> <div class="address-icon">${icons[address.type] || '📍'}</div> <div class="address-info"> <h3> ${address.label} ${address.isDefault ? '<span class="default-badge">★ Default</span>' : ''} </h3> <p class="address-text">${address.formattedAddress}</p> <p class="postcode">Postcode: ${address.components.postcode}</p> </div> </div> <div class="address-actions"> <button onclick="openEditForm('${address.id}')" title="Edit">✏️</button> ${!address.isDefault ? `<button onclick="setDefaultAddress('${address.id}')" title="Set as default">⭐</button>` : ''} <button onclick="deleteAddress('${address.id}')" title="Delete">🗑️</button> </div> </div> `).join(''); } // Event listeners document.getElementById('add-address-btn').addEventListener('click', openAddForm); document.getElementById('address-form').addEventListener('submit', saveAddress); document.getElementById('cancel-btn').addEventListener('click', closeModal); document.getElementById('address-type').addEventListener('change', (e) => { const customLabelField = document.getElementById('custom-label-field'); customLabelField.style.display = e.target.value === 'other' ? 'block' : 'none'; }); // Make functions globally available window.openEditForm = openEditForm; window.deleteAddress = deleteAddress; window.setDefaultAddress = setDefaultAddress; // Load addresses on page load loadAddresses(); }); </script> <!-- Add Address Button --> <button id="add-address-btn" class="btn-primary">+ Add New Address</button> <!-- Address List --> <div id="address-list" class="address-list"> <!-- Addresses will be rendered here --> </div> <!-- Modal --> <div id="modal" class="modal hidden"> <div class="modal-content"> <h2 id="form-title">Add New Address</h2> <form id="address-form"> <!-- Address Type --> <div class="form-group"> <label>Address Type</label> <select id="address-type"> <option value="home">Home</option> <option value="work">Work</option> <option value="other">Other</option> </select> </div> <!-- Custom Label (shown if Other selected) --> <div id="custom-label-field" class="form-group" style="display: none;"> <label>Custom Label</label> <input type="text" id="custom-label" placeholder="e.g., Mum's House"> </div> <!-- Address Autocomplete --> <div class="form-group"> <label>Search Address</label> <div id="address-autocomplete"></div> </div> <!-- Hidden field for formatted address --> <input type="hidden" id="formatted-address"> <input type="hidden" id="coordinates"> <!-- Address Fields --> <div class="form-row"> <div class="form-group"> <label>Street Number</label> <input type="text" id="street-number"> </div> <div class="form-group"> <label>Street Name</label> <input type="text" id="street-name"> </div> </div> <div class="form-row"> <div class="form-group"> <label>City</label> <input type="text" id="city"> </div> <div class="form-group"> <label>County</label> <input type="text" id="county"> </div> </div> <div class="form-row"> <div class="form-group"> <label>Postcode</label> <input type="text" id="postcode"> </div> <div class="form-group"> <label>Country</label> <input type="text" id="country"> </div> </div> <!-- Set as Default --> <div class="form-group"> <label class="checkbox-label"> <input type="checkbox" id="is-default"> Set as default address </label> </div> <!-- Actions --> <div class="form-actions"> <button type="button" id="cancel-btn" class="btn-secondary">Cancel</button> <button type="submit" class="btn-primary">Save Address</button> </div> </form> </div> </div> <style> .btn-primary { background: #4f46e5; color: white; padding: 0.75rem 1.5rem; border-radius: 0.5rem; border: none; cursor: pointer; font-weight: 500; } .address-list { margin-top: 1.5rem; display: flex; flex-direction: column; gap: 1rem; } .address-card { padding: 1rem; border: 1px solid #e5e7eb; border-radius: 0.5rem; background: white; display: flex; justify-content: space-between; align-items: start; } .address-header { display: flex; gap: 1rem; flex: 1; } .address-icon { font-size: 1.5rem; width: 2.5rem; height: 2.5rem; display: flex; align-items: center; justify-content: center; background: #f3f4f6; border-radius: 0.5rem; } .address-info h3 { font-weight: 600; margin-bottom: 0.5rem; } .default-badge { background: #dcfce7; color: #166534; padding: 0.25rem 0.5rem; border-radius: 0.25rem; font-size: 0.75rem; margin-left: 0.5rem; } .address-text { color: #6b7280; font-size: 0.875rem; } .postcode { color: #9ca3af; font-size: 0.75rem; margin-top: 0.25rem; } .address-actions { display: flex; gap: 0.5rem; } .address-actions button { background: #f3f4f6; border: none; padding: 0.5rem; border-radius: 0.375rem; cursor: pointer; font-size: 1rem; } .address-actions button:hover { background: #e5e7eb; } .modal { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.5); display: flex; align-items: center; justify-content: center; z-index: 1000; } .modal.hidden { display: none; } .modal-content { background: white; padding: 2rem; border-radius: 0.5rem; max-width: 600px; width: 90%; max-height: 90vh; overflow-y: auto; } .form-group { margin-bottom: 1rem; } .form-group label { display: block; font-weight: 500; margin-bottom: 0.5rem; color: #374151; } .form-group input, .form-group select { width: 100%; padding: 0.5rem; border: 1px solid #d1d5db; border-radius: 0.375rem; } .form-row { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; } .checkbox-label { display: flex; align-items: center; gap: 0.5rem; cursor: pointer; } .checkbox-label input { width: auto; } .form-actions { display: flex; justify-content: flex-end; gap: 0.75rem; margin-top: 1.5rem; } .btn-secondary { background: #f3f4f6; color: #374151; padding: 0.75rem 1.5rem; border-radius: 0.5rem; border: none; cursor: pointer; } .empty-state { text-align: center; padding: 3rem; color: #6b7280; } </style>