Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

To update a page when a native <select> changes, submit its form rather than calling location.reload(). For filters and other bookmarkable state, use a GET form:

<form method="get" action="/products">
  <label for="category">Category</label>
  <select id="category" name="category">
    <option value="">All categories</option>
    <option value="books">Books</option>
    <option value="games">Games</option>
  </select>

  <noscript><button type="submit">Apply</button></noscript>
</form>

<script>
  document.querySelector("#category").addEventListener("change", (event) => {
    event.target.form.requestSubmit();
  });
</script>

A selection such as books navigates to /products?category=books. The server must then read that parameter and render the matching option with selected.

Refresh versus submit: the important distinction

These three operations are different:

  • location.reload() reloads the current URL. It does not serialize the current form controls into the request.
  • form.requestSubmit() submits the form using its action, method, validation rules, and successful controls.
  • location.assign(url) navigates to a URL that your code has constructed.

If the real requirement is “send the newly selected value to the server,” form submission is usually the correct solution. See the MDN documentation for Location.reload() and the HTMLFormElement documentation.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

The recommended GET pattern

Use GET when the selection filters, sorts, searches, or otherwise describes the page being viewed. The resulting URL is bookmarkable, shareable, and naturally works with browser Back and Forward navigation.

<form id="filter-form" method="get" action="/products">
  <label for="category">Category</label>
  <select id="category" name="category">
    <option value="">All categories</option>
    <option value="books">Books</option>
    <option value="games">Games</option>
  </select>

  <noscript>
    <button type="submit">Apply</button>
  </noscript>
</form>

<script>
  document.querySelector("#category").addEventListener("change", (event) => {
    event.target.form.requestSubmit();
  });
</script>

A native select fires change when the user commits a new option, and its value contains the selected option’s value. The control needs a name to contribute a parameter during form submission. A select can be associated with a form by nesting it inside the form or by using its form attribute. The MDN select reference documents these behaviors.

Why use requestSubmit()?

requestSubmit() follows the normal submission path: submit handlers run and constraint validation is applied. form.submit() submits directly and skips those behaviors. Use submit() only when bypassing validation and submit events is intentional:

select.form.requestSubmit();

// Direct submission, without normal submit handling:
select.form.submit();

For a short legacy-compatible inline handler, this is valid when the select belongs to a form:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
<select name="category" onchange="this.form.requestSubmit()">
  <option value="books">Books</option>
  <option value="games">Games</option>
</select>

Preserve the selected option after navigation

Submitting the form changes the request, but it does not tell the server how to render the next document. Read the query parameter, validate it, and mark the matching option as selected.

category = request.query.category

for option in categories:
    option.selected = (option.value == category)

The generated HTML should look like this when the URL is /products?category=books:

<select name="category">
  <option value="">All categories</option>
  <option value="books" selected>Books</option>
  <option value="games">Games</option>
</select>

In PHP, the same principle could be expressed as:

<?php
$category = $_GET['category'] ?? '';
$categories = [
    'books' => 'Books',
    'games' => 'Games',
];
?>

<select name="category" id="category">
  <option value="">All categories</option>
  <?php foreach ($categories as $value => $label): ?>
    <option value="<?= htmlspecialchars($value, ENT_QUOTES, 'UTF-8') ?>"<?= $category === $value ? ' selected' : '' ?>>
      <?= htmlspecialchars($label, ENT_QUOTES, 'UTF-8') ?>
    </option>
  <?php endforeach; ?>
</select>

The same approach applies with req.query.category in Express, a query parameter in Flask or Django, or a model-bound value in ASP.NET. Do not hard-code selected on multiple options in a single-select control.

Multiple dropdowns: submit the whole form

When several selects describe one filter, put them in the same GET form. This avoids fragile string concatenation and automatically encodes values.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
<form id="filters" method="get" action="/results">
  <label for="country">Country</label>
  <select name="country" id="country">
    <option value="">Any country</option>
    <option value="us">United States</option>
    <option value="ca">Canada</option>
  </select>

  <label for="province">State or province</label>
  <select name="province" id="province">
    <option value="">Any state or province</option>
    <option value="ny">New York</option>
    <option value="on">Ontario</option>
  </select>
</form>

<script>
  document.querySelectorAll("#filters select").forEach((select) => {
    select.addEventListener("change", () => select.form.requestSubmit());
  });
</script>

The URL may become /results?country=us&province=ny. A multiple select normally produces repeated keys such as tag=javascript&tag=forms; ensure the server parses that parameter as a list.

Existing query parameters

Rebuilding a URL with string concatenation can discard existing state:

// Fragile: loses language, sorting, pagination, and other parameters
location.href = "/page?category=" + value;

A complete GET form is preferable when all relevant state is represented by controls. Hidden inputs can preserve fixed values:

<form method="get" action="/page">
  <input type="hidden" name="language" value="en">
  <input type="hidden" name="sort" value="price">
  <select name="category" id="category">...</select>
</form>

When you must modify the current URL directly, use URL and URLSearchParams:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
const url = new URL(window.location.href);
const select = document.querySelector("#category");

url.searchParams.set(select.name, select.value);
window.location.assign(url);

set() replaces all existing values for that key. Use append() when repeated values are intentional. These APIs also handle URL encoding for spaces, ampersands, Unicode, and other reserved characters.

Placeholder options and required selects

A placeholder is a user-interface choice, not a technical requirement for the change event:

<select name="country" id="country" required>
  <option value="">Choose a country</option>
  ...
</select>

If an empty choice should not submit immediately, handle it explicitly:

select.addEventListener("change", (event) => {
  if (!event.target.value) return;
  event.target.form.requestSubmit();
});

When automatic submission is the wrong design

Do not attach an automatic change handler to a long form merely because it contains a dropdown. It may submit incomplete fields, trigger validation, expose values in a URL, or perform an unintended POST action.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Separate a live filter from a transactional form:

<form method="get" action="/price">
  <label for="plan">Plan</label>
  <select name="plan" id="plan">
    <option value="basic">Basic</option>
    <option value="pro">Pro</option>
  </select>
</form>

<form method="post" action="/register">
  <!-- registration fields and an explicit submit button -->
</form>

Use POST when the operation changes server-side data or performs an action. Require an explicit confirmation or submit button for destructive, paid, or otherwise irreversible operations. A dropdown itself is not dangerous; the risk comes from automatically invoking the form’s server-side behavior.

Partial updates with fetch()

If changing the select should update only a price, preview, or dependent control, a full navigation may be unnecessary. Fetch the new data and update a specific element:

<label for="plan">Plan</label>
<select id="plan" name="plan">
  <option value="basic">Basic</option>
  <option value="pro">Pro</option>
</select>
<p id="price" aria-live="polite"></p>

<script>
  const plan = document.querySelector("#plan");
  const price = document.querySelector("#price");

  plan.addEventListener("change", async () => {
    price.textContent = "Loading…";

    try {
      const response = await fetch(
        `/api/price?plan=${encodeURIComponent(plan.value)}`,
        { headers: { Accept: "application/json" } }
      );

      if (!response.ok) throw new Error("Request failed");

      const data = await response.json();
      price.textContent = data.displayPrice;
    } catch {
      price.textContent = "Unable to load the price.";
    }
  });
</script>

AJAX avoids a full-page navigation but adds loading, error, accessibility, and state-management responsibilities. Where practical, retain a normal server-rendered form fallback. For dependent dropdowns, disable the child select while loading, replace its options with server-approved values, handle empty and failed responses, and validate the relationship on the server.

Common errors and fixes

  • No form association: select.form is null. Nest the select in a form or add form="form-id".
  • Missing name: the control will not provide the expected server parameter.
  • Using reload(): submit the form or update the URL first.
  • Lost query state: use a complete GET form or URLSearchParams.
  • Unencoded values: avoid manual concatenation; use the URL APIs.
  • Wrong event: use change, not onselect, for a native dropdown. See MDN’s change event reference.
  • Global-name assumptions: do not expect name="type" or another named control to become a reliable JavaScript variable.

Prefer explicit references:

const form = document.querySelector("#filters");
const typeSelect = form.elements.namedItem("type");

Names such as action, method, and elements can also create confusing property collisions. Explicit IDs and the form’s elements collection are clearer.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

History, accessibility, and progressive enhancement

Each location.assign() navigation creates a history entry. That is often useful for filters, but it can make the Back button noisy if users change several values in succession. history.replaceState(null, "", url) replaces the current history entry, but it changes neither the page content nor the server response by itself.

Use a real <label>, keep the native keyboard-compatible select unless a custom control is genuinely necessary, and provide a normal submit button for users without JavaScript. When the server renders controls from URL parameters, Back and Forward navigation restore the correct selection naturally.

Validate every value on the server

A dropdown limits choices in the browser but does not make submitted data trustworthy. Validate that the value is allowed, reject unknown IDs, authorize access to the selected record or price, safely encode values rendered into HTML, and use parameterized database queries instead of concatenating query-string input into SQL.

Client-side checks improve convenience only. They are not security controls.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

The practical decision guide

Requirement Recommended approach Trade-off
Shareable filter or search state GET form Full navigation
Server-rendered page change GET form or URL navigation Requires a server response
Data mutation or action POST with an explicit submit Less convenient to auto-trigger
Price or preview update fetch() More client-side error handling
Preserve unrelated URL parameters URL/URLSearchParams Must define replacement versus append behavior
Preserve unsaved fields Separate forms, AJAX, or server repopulation Requires deliberate state design

The original SitePoint discussion, started on September 20, 2004, is a useful historical record of this problem, but its inline handlers, self.location, manual query-string construction, and reload(true) examples should not be copied into new code. The modern answer is to decide where the state belongs, then use the platform mechanism designed for that state: URL parameters for filters, forms for submissions, server rendering for persistence, and fetch() for genuinely partial updates. Read the original SitePoint thread.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.