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.
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.
#1 Best Overall
<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:
<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:
Rank #2
<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.
<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:
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.
Outdated Drivers Are Slowing You Down
One free scan finds every outdated or missing driver and matches the right update for your exact hardware.Free scan · exact hardware matchWindows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstallRank #4
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.formisnull. Nest the select in a form or addform="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, notonselect, 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.
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.
Best Value
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.
Do these 3 things before closing this tab:
1Fix the driver behind crashes, sound loss and screen glitches2Repair Windows errors before they cause bigger problems3Scan for outdated or missing drivers - takes under a minuteThe 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.
Quick Recap
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.

