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
powered by Google
Shipping address is the same as billing address

Shipping Address

powered by
powered by Google

How It Works

This example demonstrates several important patterns for managing multiple address inputs.

  1. Separate Component Instances: Each address (billing and shipping) has its own PlacesAutocomplete instance with its own response handler. This keeps the data flow clean and predictable.
  2. Shared Parsing Logic: Create a reusable parseAddressComponents function to extract address fields from the API response. This ensures consistency when populating both forms.
  3. Conditional Rendering: Use Svelte's {#if !sameAsBilling} block to show/hide the shipping address form based on the checkbox state.
  4. Address Synchronisation: When "same as billing" is checked, copy the billing address to shipping using the spread operator { ...billingAddress } to create an independent copy.
  5. 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>
...