Billing & Shipping Address
E-commerce applications commonly require both billing and shipping addresses. This example demonstrates managing multiple autocomplete instances, implementing a "same as billing" feature, and synchronising form state across components.
Live Demo
Fill in the billing address, then try checking the "same as billing" option to see the shipping address auto-populate. Uncheck it to enter a different shipping address.
Billing Address
powered by
Shipping Address
powered by
How It Works
This example demonstrates several important patterns for managing multiple address inputs.
- Separate Component Instances: Each address (billing and shipping) has its own
PlacesAutocompleteinstance with its own response handler. This keeps the data flow clean and predictable. - Shared Parsing Logic: Create a reusable
parseAddressComponentsfunction to extract address fields from the API response. This ensures consistency when populating both forms. - Conditional Rendering: Use Svelte's
{#if !sameAsBilling}block to show/hide the shipping address form based on the checkbox state. - Address Synchronisation: When "same as billing" is checked, copy the billing address to shipping using the spread
operator
{ ...billingAddress }to create an independent copy. - State Management: Track both addresses independently in reactive state objects. This allows for validation, comparison, and separate submission to your backend.
<script>
import { PlacesAutocomplete } from 'places-autocomplete-js';
document.addEventListener('DOMContentLoaded', () => {
try {
let billingAutocomplete;
let shippingAutocomplete;
// Parse address components into a structured object
const parseAddressComponents = (components) => {
const mapped = components.reduce((acc, component) => {
const type = component.types[0];
acc[type] = component.longText;
return acc;
}, {});
return {
street_number: mapped.street_number || mapped.point_of_interest || '',
street_name: mapped.route || '',
city: mapped.postal_town || mapped.locality || '',
county: mapped.administrative_area_level_1 || '',
postcode: mapped.postal_code || '',
country: mapped.country || ''
};
};
// Populate form fields from address object
const populateFormFields = (prefix, address) => {
const setInputValue = (id, value) => {
const element = document.getElementById(id);
if (element) {
element.value = value;
}
};
setInputValue(`${prefix}-address1`, address.street_number);
setInputValue(`${prefix}-address2`, address.street_name);
setInputValue(`${prefix}-city`, address.city);
setInputValue(`${prefix}-county`, address.county);
setInputValue(`${prefix}-postcode`, address.postcode);
setInputValue(`${prefix}-country`, address.country);
};
// Initialise billing address autocomplete
billingAutocomplete = new PlacesAutocomplete({
containerId: 'billing-autocomplete-container',
googleMapsApiKey: 'YOUR_GOOGLE_MAPS_API_KEY', // Replace with your actual key
onResponse: (placeDetails) => {
const billingAddress = parseAddressComponents(placeDetails.addressComponents);
populateFormFields('billing', billingAddress);
// If "same as billing" is checked, update shipping too
const sameAsBillingCheckbox = document.getElementById('same-as-billing');
if (sameAsBillingCheckbox && sameAsBillingCheckbox.checked) {
populateFormFields('shipping', billingAddress);
}
},
onError: (error) => {
console.error('Billing Autocomplete Error:', error.message || error);
},
requestParams: {
region: 'GB',
includedRegionCodes: ['GB']
},
options: {
placeholder: 'Start typing your address...',
clear_input: false
}
});
// Initialise shipping address autocomplete
const initShippingAutocomplete = () => {
const container = document.getElementById('shipping-autocomplete-container');
if (container && !shippingAutocomplete) {
shippingAutocomplete = new PlacesAutocomplete({
containerId: 'shipping-autocomplete-container',
googleMapsApiKey: 'YOUR_GOOGLE_MAPS_API_KEY', // Replace with your actual key
onResponse: (placeDetails) => {
const shippingAddress = parseAddressComponents(placeDetails.addressComponents);
populateFormFields('shipping', shippingAddress);
},
onError: (error) => {
console.error('Shipping Autocomplete Error:', error.message || error);
},
requestParams: {
region: 'GB',
includedRegionCodes: ['GB']
},
options: {
placeholder: 'Start typing your address...',
clear_input: false
}
});
}
};
// Handle "same as billing" checkbox change
const sameAsBillingCheckbox = document.getElementById('same-as-billing');
const shippingSection = document.getElementById('shipping-section');
if (sameAsBillingCheckbox && shippingSection) {
sameAsBillingCheckbox.addEventListener('change', (e) => {
if (e.target.checked) {
// Hide shipping section and copy billing address
shippingSection.style.display = 'none';
// Copy billing values to shipping
const billingFields = ['address1', 'address2', 'city', 'county', 'postcode', 'country'];
billingFields.forEach(field => {
const billingInput = document.getElementById(`billing-${field}`);
const shippingInput = document.getElementById(`shipping-${field}`);
if (billingInput && shippingInput) {
shippingInput.value = billingInput.value;
}
});
} else {
// Show shipping section and reinitialise autocomplete
shippingSection.style.display = 'block';
// Clear shipping fields
const shippingFields = ['address1', 'address2', 'city', 'county', 'postcode', 'country'];
shippingFields.forEach(field => {
const shippingInput = document.getElementById(`shipping-${field}`);
if (shippingInput) {
shippingInput.value = '';
}
});
// Reinitialise shipping autocomplete after DOM updates
setTimeout(() => {
initShippingAutocomplete();
}, 100);
}
});
}
// Initialise shipping autocomplete if checkbox is not checked
if (!sameAsBillingCheckbox || !sameAsBillingCheckbox.checked) {
initShippingAutocomplete();
}
} catch (error) {
console.error('Failed to initialise PlacesAutocomplete:', error.message);
}
});
</script>
...
<!-- Billing Address Section -->
<h3>Billing Address</h3>
<div id="billing-autocomplete-container"></div>
<div>
<label for="billing-address1">Street Number:</label>
<input type="text" id="billing-address1">
</div>
<div>
<label for="billing-address2">Street Name:</label>
<input type="text" id="billing-address2">
</div>
<div>
<label for="billing-city">City:</label>
<input type="text" id="billing-city">
</div>
<div>
<label for="billing-county">State / Province:</label>
<input type="text" id="billing-county">
</div>
<div>
<label for="billing-postcode">Postcode / ZIP:</label>
<input type="text" id="billing-postcode">
</div>
<div>
<label for="billing-country">Country:</label>
<input type="text" id="billing-country">
</div>
<!-- Same as Billing Checkbox -->
<div>
<label>
<input type="checkbox" id="same-as-billing">
Shipping address is the same as billing address
</label>
</div>
<!-- Shipping Address Section -->
<div id="shipping-section">
<h3>Shipping Address</h3>
<div id="shipping-autocomplete-container"></div>
<div>
<label for="shipping-address1">Street Number:</label>
<input type="text" id="shipping-address1">
</div>
<div>
<label for="shipping-address2">Street Name:</label>
<input type="text" id="shipping-address2">
</div>
<div>
<label for="shipping-city">City:</label>
<input type="text" id="shipping-city">
</div>
<div>
<label for="shipping-county">State / Province:</label>
<input type="text" id="shipping-county">
</div>
<div>
<label for="shipping-postcode">Postcode / ZIP:</label>
<input type="text" id="shipping-postcode">
</div>
<div>
<label for="shipping-country">Country:</label>
<input type="text" id="shipping-country">
</div>
</div>
...