Real-World AJAX Examples
Example 1: Live Search / Autocomplete
A live search box that queries the server as the user types and displays suggestions in a dropdown — debounced to avoid flooding the server with requests.
<div class="search-wrapper" style="position:relative; max-width:400px;">
<input type="text" id="live-search" placeholder="Search products..."
autocomplete="off" class="form-control">
<ul id="search-results" style="
position:absolute; top:100%; left:0; right:0;
background:#fff; border:1px solid #ddd; border-radius:4px;
list-style:none; margin:0; padding:0; z-index:100; display:none;">
</ul>
</div>
const input = document.getElementById('live-search');
const resultsList = document.getElementById('search-results');
let debounceTimer;
let currentController = null;
input.addEventListener('input', function () {
const query = this.value.trim();
clearTimeout(debounceTimer);
// Cancel any in-flight request
if (currentController) currentController.abort();
if (query.length < 2) {
resultsList.style.display = 'none';
resultsList.innerHTML = '';
return;
}
debounceTimer = setTimeout(async () => {
currentController = new AbortController();
try {
const res = await fetch(
`/api/search?q=${encodeURIComponent(query)}`,
{ signal: currentController.signal }
);
const items = await res.json();
if (items.length === 0) {
resultsList.innerHTML = '<li style="padding:8px 12px;color:#999">No results</li>';
} else {
resultsList.innerHTML = items.map(item => `
<li data-id="${item.id}" style="padding:8px 12px;cursor:pointer;border-bottom:1px solid #eee"
onmouseover="this.style.background='#f5f5f5'"
onmouseout="this.style.background=''">
${item.name}
</li>
`).join('');
resultsList.querySelectorAll('li[data-id]').forEach(li => {
li.addEventListener('click', () => {
input.value = li.textContent.trim();
resultsList.style.display = 'none';
console.log('Selected ID:', li.dataset.id);
});
});
}
resultsList.style.display = 'block';
} catch (err) {
if (err.name !== 'AbortError') console.error('Search error:', err);
}
}, 300);
});
// Hide results when clicking outside
document.addEventListener('click', e => {
if (!e.target.closest('.search-wrapper')) {
resultsList.style.display = 'none';
}
});
Example 2: Infinite Scroll / Load More
Load additional content when the user scrolls to the bottom of the page — a pattern used by social media feeds and product listings.
let currentPage = 1;
let isLoading = false;
let hasMore = true;
const feed = document.getElementById('post-feed');
const sentinel = document.getElementById('scroll-sentinel'); // empty div at bottom
// IntersectionObserver fires when sentinel enters the viewport
const observer = new IntersectionObserver(async (entries) => {
if (entries[0].isIntersecting && !isLoading && hasMore) {
await loadMorePosts();
}
}, { threshold: 0.1 });
observer.observe(sentinel);
async function loadMorePosts() {
isLoading = true;
sentinel.innerHTML = '<div class="spinner">Loading...</div>';
try {
const res = await fetch(`/api/posts?page=${currentPage}&limit=10`);
const { posts, totalPages } = await res.json();
posts.forEach(post => {
const article = document.createElement('article');
article.className = 'post-card';
article.innerHTML = `
<h3>${post.title}</h3>
<p>${post.excerpt}</p>
<small>By ${post.author} — ${post.date}</small>
`;
feed.appendChild(article);
});
currentPage++;
hasMore = currentPage <= totalPages;
sentinel.innerHTML = hasMore
? '' // clear spinner, observer will trigger again
: '<p style="text-align:center;color:#999">No more posts</p>';
} catch (err) {
sentinel.innerHTML = '<p class="text-danger">Failed to load posts</p>';
console.error(err);
} finally {
isLoading = false;
}
}
// Load first page on startup
loadMorePosts();
Example 3: Real-Time Form Validation
Validate form fields against the server in real time — checking for duplicate emails, weak passwords, or invalid coupon codes as the user fills in the form.
// Reusable debounced validator
function createValidator(inputEl, feedbackEl, endpoint, paramName, minLength = 3) {
let timer;
inputEl.addEventListener('blur', () => validate()); // also validate on blur
inputEl.addEventListener('input', () => {
clearTimeout(timer);
const value = inputEl.value.trim();
if (value.length < minLength) {
setFeedback(feedbackEl, '', 'neutral');
return;
}
setFeedback(feedbackEl, 'Checking...', 'neutral');
timer = setTimeout(() => validate(), 400);
});
async function validate() {
const value = inputEl.value.trim();
if (value.length < minLength) return;
try {
const res = await fetch(`${endpoint}?${paramName}=${encodeURIComponent(value)}`);
const { valid, message } = await res.json();
setFeedback(feedbackEl, message, valid ? 'success' : 'error');
inputEl.dataset.valid = valid;
} catch {
setFeedback(feedbackEl, 'Could not validate', 'neutral');
}
}
}
function setFeedback(el, message, type) {
el.textContent = message;
el.className = `feedback feedback-${type}`;
}
// Initialize validators
createValidator(
document.getElementById('email'),
document.getElementById('email-feedback'),
'/api/validate/email', 'email', 5
);
createValidator(
document.getElementById('coupon'),
document.getElementById('coupon-feedback'),
'/api/validate/coupon', 'code', 4
);
// Prevent form submission if any field is invalid
document.getElementById('register-form').addEventListener('submit', function (e) {
const fields = this.querySelectorAll('[data-valid]');
const allValid = Array.from(fields).every(f => f.dataset.valid === 'true');
if (!allValid) {
e.preventDefault();
document.getElementById('form-error').textContent = 'Please fix the errors above.';
}
});
Example 4: Dynamic Content Loading (Tabs without Page Reload)
Load tab content on demand via AJAX — only fetching data when the user clicks a tab, and caching it so subsequent clicks don't re-fetch.
// HTML structure expected:
// <div class="tab-nav">
// <button class="tab-btn active" data-tab="overview" data-url="/api/tabs/overview">Overview</button>
// <button class="tab-btn" data-tab="reviews" data-url="/api/tabs/reviews">Reviews</button>
// <button class="tab-btn" data-tab="specs" data-url="/api/tabs/specs">Specs</button>
// </div>
// <div id="tab-content"></div>
const tabCache = {};
const tabContent = document.getElementById('tab-content');
document.querySelectorAll('.tab-btn').forEach(btn => {
btn.addEventListener('click', async function () {
const tabId = this.dataset.tab;
const url = this.dataset.url;
// Update active state
document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
this.classList.add('active');
// Serve from cache if available
if (tabCache[tabId]) {
tabContent.innerHTML = tabCache[tabId];
return;
}
// Show loading state
tabContent.innerHTML = '<div class="tab-loading"><span class="spinner"></span> Loading...</div>';
try {
const res = await fetch(url);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const html = await res.text(); // tabs return HTML fragments
tabCache[tabId] = html; // cache for future clicks
tabContent.innerHTML = html;
} catch (err) {
tabContent.innerHTML = `<p class="text-danger">Failed to load tab: ${err.message}</p>`;
}
});
});
// Load the default active tab on page load
const defaultTab = document.querySelector('.tab-btn.active');
if (defaultTab) defaultTab.click();
Ready to Level Up Your Skills?
Explore 500+ free tutorials across 20+ languages and frameworks.