How to Load Data Without Refreshing the Page Using jQuery AJAX

How I load data without a page refresh using jQuery AJAX: .load() with a fragment selector, a JSON "Load more" button, live search that aborts stale requests, a spinner, IE caching, delegated handlers and a working back button.

Executive Summary: I use two ways to load data without page refresh in jQuery. One asks the server for an HTML fragment and inserts it with .load(). The other asks for JSON with $.getJSON() and builds the markup in the browser. Both need a loading indicator, aborted stale requests, cache: false for Internet Explorer, and delegated handlers. Real links stay underneath. Tested on jQuery 1.11.0.
Load data without page refresh illustration: a browser window with a dotted line pulling a new content card from a cloud, next to a loading ring

The question I get: “Clicking a link or typing in a box reloads my whole page. I want only the list or the panel to change. How do I load data without page refresh in jQuery, without breaking the back button or search engines?” The mechanics are easy. The details around them are where pages go wrong.

To load data without a page refresh, you send an HTTP request from JavaScript and put the response into an existing element. jQuery gives you two routes. .load() fetches HTML and inserts it. The lower-level $.ajax() family fetches data you format yourself. The .load() documentation covers the first. My jQuery AJAX tutorial covers the second, with the failure cases.

I ran every snippet below on jQuery 1.11.0 against a local test server, and the output is real. The test page has a #target panel, an empty #list, a “Load more” button, a search box and a #status line.

My position: build the page to work with ordinary links and forms first, then add AJAX on top. If page two exists only behind a JavaScript click handler, search engines never see it. Neither does a visitor with a broken script, or someone who wants to bookmark it. Progressive enhancement is what makes the AJAX version safe to ship.

Route 1: .load() for a ready-made HTML fragment

// .load() with a selector after the URL: only that part of the response is inserted
$('#target').load('/fragment.html #news', function (responseText, textStatus, jqXHR) {
  console.log('status          :', textStatus, jqXHR.status);
  console.log('inserted HTML   :', $('#target').html());
  console.log('script executed :', window.__ran === undefined ? 'no (scripts are stripped when a selector is used)' : 'yes');
});
status          : success 200
inserted HTML   : <div id="news"><p>Story one</p><p>Story two</p></div>
script executed : no (scripts are stripped when a selector is used)

The line to notice is the URL: '/fragment.html #news', with a space and a selector after the address. jQuery fetches the whole response but inserts only the element matching #news. This is the quickest way to load data without page refresh when a normal, full page already exists. The same URL serves a complete page to a browser and a fragment to your script. The callback receives the response text, a status and the jqXHR object, as in a normal request.

The output shows a side effect too. The response contained an inline <script>, and with a selector jQuery removed it before inserting. Without a selector, the behaviour changes:

// .load() without a selector: the whole response goes in, and inline scripts run
$('#target').load('/fragment.html', function () {
  console.log('children        :', $('#target').children().map(function () { return this.id; }).get().join(', '));
  console.log('script executed :', window.__ran === 1 ? 'yes, once' : window.__ran);
});
children        : header, news, 
script executed : yes, once

Loading the whole response ran the script once. That matters for security. .load() without a selector runs whatever script the server sends. So point it only at your own server’s markup, and never at a URL that includes text a user typed. The empty entry at the end of the children list is the script element jQuery ran and then removed.

Route 2: load data without page refresh as JSON

var $btn = $('#more');
$btn.on('click', function () {
  var next = $btn.data('next');
  $btn.prop('disabled', true);
  $.getJSON('/page/' + next)
    .done(function (data) {
      $.each(data.items, function (i, text) { $('#list').append($('<li>').text(text)); });
      if (data.next) { $btn.data('next', data.next).prop('disabled', false); }
      else { $btn.hide(); }
    })
    .fail(function () { $btn.prop('disabled', false); });
});
$btn.click();
setTimeout(function () {
  console.log('after click 1:', $('#list li').length, 'items | button disabled:', $btn.prop('disabled'), '| next =', $btn.data('next'));
  $btn.click();
  setTimeout(function () {
    console.log('after click 2:', $('#list li').map(function () { return $(this).text(); }).get().join(','), '| button visible:', $btn.css('display') !== 'none');
  }, 200);
}, 250);
after click 1: 3 items | button disabled: false | next = 2
after click 2: one,two,three,four,five | button visible: false

Here the server returns data instead of markup: {"items": [...], "next": 2}. The click handler asks for the page named in a data-next attribute. It appends one list item per entry with .text(), so a title with angle brackets cannot turn into markup. Then it updates the button. The button stays disabled while the request runs, so a double-click does not fetch page two twice. When the server says there is no next page (next: null), the button hides. The server decides when to stop, and the script never guesses. This is how I load data without page refresh on most list pages.

I pick JSON when the same data feeds more than one view, when the HTML would be large next to the data, or when I want to format values in the browser. HTML fragments win when the markup is complex, your server templates already build it, and you want one place to maintain it. If you insert many items, build the markup as one string and insert it once.

Live search: cancel the old request

var current = null, log = [];
function search(term, ms) {
  if (current) { current.abort(); }
  current = $.getJSON('/slow', { ms: ms, q: term })
    .done(function () { log.push('done(' + term + ')'); })
    .fail(function (xhr, status) { log.push('fail(' + term + ':' + status + ')'); });
}
search('j', 400);
search('jq', 300);
search('jque', 100);
setTimeout(function () { console.log(log.join('  ')); }, 700);
fail(j:abort)  fail(jq:abort)  done(jque)

This bug appears only on slow connections. A visitor types “j”, “jq”, “jque”, and three requests fly out. Responses do not arrive in order. The answer for “j” can land after the answer for “jque”, and the page shows results for the wrong text. The fix: keep the current request in a variable and call .abort() on it before starting the next. In the output, jQuery aborted the first two requests (their .fail() ran with abort), and only the latest one delivered data. Treat abort as a normal outcome in your .fail() handler and show no error for it.

Start the request after a short pause instead of on every key press. Wait about 250 milliseconds after the last keystroke with setTimeout and clearTimeout. Live search is the hardest place to load data without page refresh well. The server then sees one request per word instead of one per letter.

A loading indicator for every request

var events = [];
$(document).ajaxStart(function () { events.push('ajaxStart'); $('#status').text('Loading…'); })
           .ajaxStop(function () { events.push('ajaxStop'); $('#status').text(''); });
$.getJSON('/json'); $.getJSON('/text'); 
setTimeout(function () { console.log(events.join(' -> '), '| status text now:', JSON.stringify($('#status').text())); }, 300);
ajaxStart -> ajaxStop | status text now: ""

Two requests started together produced one ajaxStart and one ajaxStop. These events mean “any request in flight,” so they suit a single spinner or a “Loading…” line, however many requests overlap. Any time you load data without page refresh, show that something is happening. If the visitor sees nothing, they click again, and you get duplicate requests.

Internet Explorer and cached responses

$.ajaxSetup({ cache: false });
$.getJSON('/count', { a: 1 }).done(function (d) {
  console.log('query sent with cache:false ->', d.query.replace(/_=\d+/, '_=<timestamp>'));
});
query sent with cache:false -> ?a=1&_=<timestamp>

With cache: false, jQuery adds a changing _=timestamp parameter to GET requests, so the URL is new every time. Without it, Internet Explorer often serves a stored copy of a GET response, and a “refresh the list” button seems to do nothing. Old IE is the main reason a page fails to load data without page refresh after the first request. Set it with $.ajaxSetup({ cache: false }) for a page of live data, or per request.

The handlers that disappear

$('#target').html('<a class="x">old</a>');
$('#target .x').on('click', function () { console.log('direct handler fired'); });
$('#target').on('click', '.x', function () { console.log('delegated handler fired'); });
$('#target').html('<a class="x">new</a>');   // replaced, like a .load() would
$('#target .x').click();
delegated handler fired

When new content replaces old content, the handlers bound to the old elements go with it. Only the delegated handler fired. If your loaded content has links or buttons, attach the handler once to #target, or another container you never replace, with the selector as the second argument. I explain why in my post on jQuery event handling. It is also a regular among jQuery interview questions.

Keep the back button and search engines working

An AJAX update changes the page but not its address. That causes three problems. The back button leaves the site. The visitor cannot bookmark or share what they see. And search engines see only the first state. Every page that loads data without page refresh has to solve them. There are two answers, and your choice depends on the browsers you support.

  • The History API. history.pushState exists in current Chrome, Firefox and Safari, and in Internet Explorer 10 and later, but not Internet Explorer 9 or earlier. Call it after each update to give the new state a real URL. Listen for popstate to reload the right content when the visitor goes back. The History API reference on MDN describes both. Feature-test with if (window.history && history.pushState) and fall back to normal links.
  • Real links underneath. Make “next page” an ordinary <a href="/news?page=2"> that works without JavaScript, and intercept the click to load it with AJAX. The server returns a full page for a normal request. It returns a fragment when it sees the X-Requested-With: XMLHttpRequest header that jQuery adds to every AJAX call. Crawlers and visitors without scripts follow the link and get a complete page.

Hashbang URLs

Older sites keep state after a # in the URL. Google documents a “hashbang” convention (#!) that lets its crawler request a static copy. That scheme works, but you must build a second, crawlable version of every page. Real links are simpler and do not depend on a crawler feature. Whichever you pick, test it. View the page with JavaScript off and check that the content is still reachable.

Also tell assistive technology that content changed. Put aria-live="polite" on the container that updates, so a screen reader announces new results.

Troubleshooting partial page loads

Symptom Cause Fix
The whole page appears inside the panel No selector after the URL in .load() Use '/page #content' or return a fragment for AJAX requests
Script in loaded content did not run A selector was used, so scripts are stripped Move behaviour into the main page and delegate events
Results do not match the text typed Responses arrived out of order Abort the previous request before starting a new one
Same data after clicking refresh in Internet Explorer GET cached cache: false
Buttons in the new content do nothing Direct handlers on replaced elements Delegate from a stable container
Back button leaves the site URL never changed Use pushState and popstate, or real links
Nothing happens when testing locally Page opened from file:// Test through a local web server

How I load data without page refresh on real sites

  • One URL, two responses. The same address returns a full page normally and only the fragment for AJAX, based on the X-Requested-With header. You maintain one template.
  • The server decides when to stop. Paged endpoints return a next value or nothing, and the script hides the button when it is missing.
  • Debounced search with cancellation. A short pause after typing, one request in flight at a time, and the previous one aborted.
  • Visible loading and failure states. A spinner while waiting, and a message with a retry button when the request fails. Never a silent, empty list.

The failure I see most often with partial loading is the live search that shows the wrong results. It works on the developer’s fast office connection, so nobody notices. Then a visitor on a slow mobile link types quickly, an early response lands last, and the list shows matches for half a word. Adding .abort() takes three lines. Testing with the browser’s network throttling is how I catch it before visitors do.

How should this update load?

  1. Is there a plain link or form that already works? Build that first, and enhance it.
  2. HTML or JSON? A fragment when the server already renders it and the markup is complex. JSON when several views share the data.
  3. Can several requests overlap? If yes, keep the current request and abort the old one.
  4. Does the visitor need the new state to have an address? If yes, use pushState where available, or real links.
  5. What does the visitor see while waiting and on failure? Decide both before writing the success path.

When not to load data without page refresh

  • Content search engines must index. Put important text in the first HTML response instead of fetching it after load.
  • A change that is really a new page. If the visitor thinks of it as going somewhere else, they expect a normal navigation with its own address.
  • Tiny lists. If the full list is a few items, include it in the page and show or hide it.

Common mistakes

  • Building a page that only works with JavaScript. Start with real links.
  • Not aborting the old request. Out-of-order responses show the wrong results.
  • Inserting server text with .html(). Use .text() for anything a user could have written.
  • Binding handlers to elements that will be replaced. Delegate.
  • Leaving out the loading and failure states. The visitor cannot tell slow from broken.
  • Ignoring the back button. A new state needs a new URL.

If you would rather load data without page refresh and skip jQuery, the native XMLHttpRequest object does the same job with more code.

FAQ

How do I load data into a div without refreshing the page in jQuery?

Call $('#target').load('/url #fragment') to fetch HTML and insert only the matching part. Or use $.getJSON(url) and build the markup yourself in .done(). Either way, add a loading indicator and handle failure.

What does jQuery .load() do with a selector after the URL?

It fetches the whole response but inserts only the elements that match the selector, such as /page.html #news. jQuery strips inline scripts from the response when you use a selector, and runs them when you do not.

How do I build a “Load more” button with jQuery?

On click, request the next page with $.getJSON('/page/' + next) and append the items with .text(). Store the next page number from the response, and hide the button when the server reports no next page. Disable the button while the request runs.

How do I stop old AJAX results from overwriting new ones?

Keep the current request in a variable and call .abort() on it before starting the next. The aborted request’s .fail() callback runs with textStatus equal to abort. Ignore that case instead of showing an error.

How do I keep the back button working with AJAX content?

Where the browser supports it, call history.pushState after each update and handle popstate to restore the earlier content. Also make the navigation real links with their own URLs, so pages work without JavaScript and search engines can reach them.

Loading part of a page takes a little jQuery and a lot of care. Cancel stale requests, show progress, delegate handlers, and keep a real address for every state. My rule of thumb when I load data without page refresh: make it work with links first, let AJAX make it faster, and abort anything the visitor has already moved past.

Originally posted on 18 March 2014 and post last modified on 2 November 2026

AJAX Front-End Development JavaScript jQuery

Share this article

Leave a Reply

Your email address will not be published. Required fields are marked *