Reload a Section of a Page Without Refreshing: jQuery .load() and fetch()

Quick Summary

  • jQuery's .load() with a URL, a space and a selector fetches the page in the background and swaps in only the matching element's content; run it in a change handler to update on every choice.
  • Without jQuery, fetch() the page, parse it with DOMParser and copy the element's innerHTML; an AbortController stops an older, slower response from overwriting a newer one.
  • Scripts in the reloaded part don't run and handlers bound to the old elements are lost, so use delegated events; each call downloads the whole page.

To reload one part of a page without refreshing the whole page, request the page again in the background and replace only that part. With jQuery it takes one line: $(selector).load(url + ' ' + selector). Without jQuery, fetch() the page, parse the response with DOMParser and copy the new content into the element. Either way the browser downloads the whole page and uses only the matching element.

The example below updates the price on a Shopify-style product page after the variant changes. It builds the URL for the selected variant, puts it in the address bar with history.pushState() (explained in changing the address bar with pushState()), then reloads the price section from that URL.

Reload a section with jQuery .load()

JavaScript
jQuery(function ($) {
 var variant = $('select.product-single__variants :selected').val();
 console.log( variant );
 var loc = window.location;
 var origin = location.origin;
 var pathname = location.pathname;
 var finalUrl = origin + pathname + "?variant=" + variant;

 //updating URL
 history.pushState({}, null, finalUrl);

 //reloading the price section
 $(".product__price-and-badge .product__price").load(finalUrl+" .product__price-and-badge .product__price>*","");
});
  • .load() requests finalUrl. Because the URL is followed by a space and a selector, jQuery uses only the matching part of the response, as described in the .load() documentation.
  • .product__price>* selects the children of the price element, so the target element stays in place and only its contents are replaced.
  • The empty second argument sends no extra data. jQuery only switches to a POST request when the data is an object, so this is a GET request.
  • jQuery(function ($) { … }) runs the code once the page is ready and passes jQuery in as $, so $ works even where jQuery runs in no-conflict mode, as on WordPress.
  • Change both selectors to match the section on your own page. The same element has to exist in the page returned from finalUrl.

Tested in headless Chrome 153 with the jQuery 3.7.1 file that WordPress 7.1.2 ships, on a test page opened with ?variant=102: the price element was filled with the $12.00 price from a background GET request to /product?variant=102.

Reload the price each time the variant changes

As written, the code runs once, when the page loads. To reload the price each time the customer picks a variant, run it in a change handler on the select:

JavaScript
jQuery(function ($) {
 $('select.product-single__variants').on('change', function () {
  var variant = $(this).val();
  var finalUrl = location.origin + location.pathname + "?variant=" + variant;

  //updating URL
  history.pushState({}, null, finalUrl);

  //reloading the price section
  $(".product__price-and-badge .product__price").load(finalUrl + " .product__price-and-badge .product__price>*");
 });
});

In the test, choosing variant 103 changed the address to /product?variant=103 and the price to $15.00.

The same without jQuery: fetch()

fetch(), DOMParser and AbortController are built into every current browser. This version also handles two things the jQuery code doesn’t: the Back button, and a slow response arriving after a newer one.

JavaScript
const priceSelector = '.product__price-and-badge .product__price';
const variantSelect = document.querySelector('select.product-single__variants');
let controller;

async function reloadSection(url, selector) {
  // Cancel a request that is still running, so an older response can't win.
  if (controller) {
    controller.abort();
  }
  controller = new AbortController();

  try {
    const response = await fetch(url, { signal: controller.signal });
    if (!response.ok) {
      throw new Error('Request failed: ' + response.status);
    }
    const html = await response.text();
    const fresh = new DOMParser().parseFromString(html, 'text/html').querySelector(selector);
    const current = document.querySelector(selector);

    if (fresh && current) {
      current.innerHTML = fresh.innerHTML;
    }
  } catch (error) {
    if (error.name !== 'AbortError') {
      console.error(error);
    }
  }
}

history.replaceState({ variant: variantSelect.value }, '');

variantSelect.addEventListener('change', function () {
  const url = new URL(window.location.href);
  url.searchParams.set('variant', variantSelect.value);

  history.pushState({ variant: variantSelect.value }, '', url);
  reloadSection(url, priceSelector);
});

window.addEventListener('popstate', function (event) {
  if (event.state && event.state.variant) {
    variantSelect.value = event.state.variant;
    reloadSection(window.location.href, priceSelector);
  }
});
  • reloadSection() fetches the page, reads it as text and parses it into a separate document with DOMParser. It then finds the same element in that document and copies its content into the element on the page, like .product__price>* does in the jQuery version.
  • fetch() doesn’t fail on a 404 or 500 response, so the function checks response.ok itself (see MDN on fetch()). On an error, the old price stays and the error goes to the console.
  • Each call cancels the previous request with its AbortController. The cancelled request ends with an AbortError, which is ignored.
  • The URL is built with new URL() and searchParams.set(), which keeps other parameters and the hash. replaceState() stores the starting variant, and the popstate handler selects the variant again and reloads the price when the visitor presses Back or Forward. Both are explained in the pushState post.

Load the script after the select, for example in the footer or with the defer attribute. Tested in Chrome 153: choosing 102 and then 103 showed $12.00 and $15.00, and pressing Back twice showed $12.00 and then the original $10.00, each with the matching URL and selected option. With the response for 102 slowed down by 800 ms, choosing 102 and then 103 straight away ended on $15.00. The jQuery change handler above, given the same slow response, ended on $12.00 while the select showed variant 103.

Should you use jQuery or fetch()?

On a WordPress site whose theme or plugins already load jQuery, .load() is still the shortest way to write this, and it costs nothing extra. On a WooCommerce variable product page jQuery is always loaded, because WooCommerce’s variation script depends on it. .load() returns the jQuery object rather than the request, though, so there’s no simple way to cancel an earlier request. If customers can switch variants quickly, or the page doesn’t load jQuery anyway, use the fetch() version.

Things to check

  • Scripts in the reloaded part don’t run. With a selector after the URL, jQuery removes the scripts before inserting the content, and scripts inserted with innerHTML never run. In the test, an inline script inside the price element ran once on page load and not again after either kind of reload.
  • Event handlers on the replaced elements are lost. A click handler bound directly to the old .money element didn’t fire after the reload; a delegated handler on the price element, $('.product__price').on('click', '.money', …), still did. See re-running scripts after Ajax content loads.
  • Each call downloads the whole page. On slow pages the update can take a moment. If you control the server, return only the part you need. On Shopify, the Section Rendering API returns the HTML of a single section when you add section_id to the URL.
Share on X (opens in a new tab) Share on LinkedIn (opens in a new tab) Share on Facebook (opens in a new tab)

ChatGPT (opens in a new tab) Claude (opens in a new tab) Gemini (opens in a new tab) Perplexity (opens in a new tab) Grok (opens in a new tab)

More Insights

See More