Aprende a construir un formulario de dirección completo con dropdowns de país → estado → ciudad usando los endpoints select de CountryDataAPI.
Esta guía te muestra cómo crear inputs de select en cascada para:
Los endpoints select están optimizados para formularios, devolviendo solo los campos id y name para un tamaño de payload mínimo y máximo rendimiento.
Primero, carga todos los países cuando tu página cargue. Los países rara vez cambian, así que puedes cachear estos datos en localStorage para mejor rendimiento.
const API_KEY = 'your-api-key';
const BASE_URL = 'https://api.countrydataapi.com/v1';
async function loadCountries() {
try {
const response = await fetch(
`${BASE_URL}/select/countries?apikey=${API_KEY}&lang=es`
);
const { success, data, error } = await response.json();
if (!success) {
console.error('Error de API:', error);
return;
}
const countrySelect = document.getElementById('country');
data.forEach(country => {
const option = new Option(country.name, country.id);
countrySelect.add(option);
});
} catch (err) {
console.error('Error de Red:', err);
}
}
// Llamar al cargar la página
document.addEventListener('DOMContentLoaded', loadCountries);
Consejo: Cachea la lista de países en localStorage para evitar llamadas repetidas a la API. ¡Los países no cambian frecuentemente!
Cuando un usuario seleccione un país, carga los estados/provincias correspondientes y resetea el dropdown de ciudad.
document.getElementById('country').addEventListener('change', async (e) => {
const countryId = e.target.value;
const stateSelect = document.getElementById('state');
const citySelect = document.getElementById('city');
// Resetear dropdowns de estado y ciudad
stateSelect.innerHTML = '<option value="">Selecciona estado...</option>';
citySelect.innerHTML = '<option value="">Selecciona ciudad...</option>';
stateSelect.disabled = true;
citySelect.disabled = true;
if (!countryId) return;
try {
const response = await fetch(
`${BASE_URL}/select/states?apikey=${API_KEY}&country=${countryId}&lang=es`
);
const { success, data, error } = await response.json();
if (!success) {
console.error('Error de API:', error);
return;
}
data.forEach(state => {
stateSelect.add(new Option(state.name, state.id));
});
stateSelect.disabled = false;
} catch (err) {
console.error('Error de Red:', err);
}
});
Finalmente, carga las ciudades cuando se seleccione un estado.
document.getElementById('state').addEventListener('change', async (e) => {
const stateId = e.target.value;
const citySelect = document.getElementById('city');
// Resetear dropdown de ciudad
citySelect.innerHTML = '<option value="">Selecciona ciudad...</option>';
citySelect.disabled = true;
if (!stateId) return;
try {
const response = await fetch(
`${BASE_URL}/select/cities?apikey=${API_KEY}&state=${stateId}&lang=es`
);
const { success, data, error } = await response.json();
if (!success) {
console.error('Error de API:', error);
return;
}
data.forEach(city => {
citySelect.add(new Option(city.name, city.id));
});
citySelect.disabled = false;
} catch (err) {
console.error('Error de Red:', err);
}
});
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Formulario de Dirección - CountryDataAPI</title>
<style>
body {
font-family: system-ui, -apple-system, sans-serif;
max-width: 600px;
margin: 50px auto;
padding: 20px;
}
.form-group {
margin-bottom: 20px;
}
label {
display: block;
margin-bottom: 8px;
font-weight: 600;
color: #333;
}
select {
width: 100%;
padding: 10px;
border: 2px solid #e2e8f0;
border-radius: 6px;
font-size: 16px;
background-color: white;
cursor: pointer;
}
select:disabled {
background-color: #f7fafc;
cursor: not-allowed;
opacity: 0.6;
}
select:focus {
outline: none;
border-color: #3b82f6;
}
button {
background-color: #3b82f6;
color: white;
padding: 12px 24px;
border: none;
border-radius: 6px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
width: 100%;
}
button:disabled {
background-color: #cbd5e0;
cursor: not-allowed;
}
</style>
</head>
<body>
<h1>Formulario de Dirección</h1>
<form id="address-form">
<div class="form-group">
<label for="country">País *</label>
<select id="country" name="country" required>
<option value="">Selecciona país...</option>
</select>
</div>
<div class="form-group">
<label for="state">Estado/Provincia *</label>
<select id="state" name="state" required disabled>
<option value="">Selecciona estado...</option>
</select>
</div>
<div class="form-group">
<label for="city">Ciudad *</label>
<select id="city" name="city" required disabled>
<option value="">Selecciona ciudad...</option>
</select>
</div>
<div class="form-group">
<label for="street">Dirección *</label>
<input type="text" id="street" name="street" required
style="width: 100%; padding: 10px; border: 2px solid #e2e8f0; border-radius: 6px;">
</div>
<button type="submit">Enviar Dirección</button>
</form>
<script src="address-form.js"></script>
</body>
</html>
Maneja el envío del formulario para obtener los valores seleccionados:
document.getElementById('address-form').addEventListener('submit', (e) => {
e.preventDefault();
const formData = new FormData(e.target);
const address = {
country: formData.get('country'),
state: formData.get('state'),
city: formData.get('city'),
street: formData.get('street')
};
console.log('Dirección enviada:', address);
// Enviar a tu backend o procesar según necesites
// fetch('/api/save-address', {
// method: 'POST',
// headers: { 'Content-Type': 'application/json' },
// body: JSON.stringify(address)
// });
});
Esta implementación es altamente eficiente con el uso de tokens:
Ejemplo: Un usuario completando el formulario usa solo 3 tokens en total.
Los países no cambian a menudo. Cachéalos en localStorage:
async function loadCountries() {
const cached = localStorage.getItem('countries');
if (cached) {
const data = JSON.parse(cached);
populateCountrySelect(data);
return;
}
const response = await fetch(
`${BASE_URL}/select/countries?apikey=${API_KEY}&lang=es`
);
const { success, data } = await response.json();
if (success) {
localStorage.setItem('countries', JSON.stringify(data));
populateCountrySelect(data);
}
}
function populateCountrySelect(countries) {
const select = document.getElementById('country');
countries.forEach(country => {
select.add(new Option(country.name, country.id));
});
}
Muestra retroalimentación visual mientras se cargan los datos:
async function loadStates(countryId) {
const stateSelect = document.getElementById('state');
stateSelect.innerHTML = '<option>Cargando...</option>';
stateSelect.disabled = true;
// ... fetch states
stateSelect.disabled = false;
}
Si agregas un dropdown con búsqueda, aplica debounce al input:
let debounceTimer;
searchInput.addEventListener('input', (e) => {
clearTimeout(debounceTimer);
debounceTimer = setTimeout(() => {
filterOptions(e.target.value);
}, 300);
});
Siempre proporciona mensajes de error amigables para el usuario:
try {
// Llamada API
} catch (err) {
const select = document.getElementById('state');
select.innerHTML = '<option>Error al cargar estados. Intenta de nuevo.</option>';
}
La API soporta múltiples idiomas. Cambia el parámetro lang:
// Español
`${BASE_URL}/select/countries?apikey=${API_KEY}&lang=es`
// Portugués
`${BASE_URL}/select/countries?apikey=${API_KEY}&lang=pt`
// Francés
`${BASE_URL}/select/countries?apikey=${API_KEY}&lang=fr`
// Alemán
`${BASE_URL}/select/countries?apikey=${API_KEY}&lang=de`
// Italiano
`${BASE_URL}/select/countries?apikey=${API_KEY}&lang=it`
Si tienes preguntas o necesitas asistencia:
Consejo Pro: Para aplicaciones en producción, considera implementar un componente de dropdown personalizado con funcionalidad de búsqueda para manejar países con muchos estados/ciudades. Librerías como Select2, Choices.js, o autocompletar personalizado pueden mejorar significativamente la UX.