Modifying a query string without reloading the page
Asked Answered
B

10

220

I am creating a photo gallery, and would like to be able to change the query string and title when the photos are browsed.

The behavior I am looking for is often seen with some implementations of continuous/infinite page, where while you scroll down the query string keeps incrementing the page number (http://x.com?page=4) etc.. This should be simple in theory, but I would like something that is safe across major browsers.

I found this great post, and was trying to follow the example with window.history.pushstate, but that doesn't seem to be working for me. And I'm not sure if it is ideal because I don't really care about modifying the browser history.

I just want to be able to offer the ability to bookmark the currently viewed photo, without reloading the page every time the photo is changed.

Here is an example of infinite page that modifies query string: http://tumbledry.org/

UPDATE found this method:

window.location.href = window.location.href + '#abc';
Beggary answered 10/6, 2012 at 15:48 Comment(5)
Can you post a link to some example site that updates its query string dynamically? I don't think it can be done, but you can change the hash value and that might be enough to get what you want.Color
possible duplicate of how to manipulate the URL with javascript/jquery?Cymbre
possible duplicate of How does GitHub change the URL but not the reload?Cymbre
possible duplicate of Why the new web Dropbox can change the URL without page refresh? and the three questions it is marked as a duplicate ofCymbre
Note that window.location.hash gives you access to the hash directly, and that's supported pretty much everywhere.Color
C
311

If you are looking for Hash modification, your solution works ok. However, if you want to change the query, you can use the pushState, as you said. Here it is an example that might help you to implement it properly. I tested and it worked fine:

if (history.pushState) {
    var newurl = window.location.protocol + "//" + window.location.host + window.location.pathname + '?myNewUrlQuery=1';
    window.history.pushState({path:newurl},'',newurl);
}

It does not reload the page, but it only allows you to change the URL query. You would not be able to change the protocol or the host values. And of course that it requires modern browsers that can process HTML5 History API.

For more information:

http://diveintohtml5.info/history.html

https://developer.mozilla.org/en-US/docs/Web/Guide/API/DOM/Manipulating_the_browser_history

Caresa answered 9/10, 2013 at 18:5 Comment(10)
I believe you can shorten window.location.protocol + '//' + window.location.host to just: window.location.origin.Hein
Also, for an addition bit of info, relative paths work as well with history.pushState(). No actual state argument is required either. Both of these mean you can do something simple like history.pushState(null, '', '/foo?bar=true') if your new url is on the same host/port.Schwing
Note that, if you don't want the new state to appear on the browser history, you can use history.replaceState() with the same arguments.Cognate
USE history.replaceState() otherwise you need to click BACK button twice in your browserParlormaid
It causes the infinite digest in AngularjJS. I don't know why and how to fix it.Submediant
I advise you to use only window.location.host + "?foo='bar'. If you are using onpopstate it will throw only /some/pathname/ and not the whole domain name. If you use window.location.protocol + "//" + window.location.host + window.location.pathname + '?myNewUrlQuery=1'; the return of onpopstate event will be something like http://www.foo.cz/bar/ which is wrong and it's not expected to be like that.Cohort
in modern browsers you can just do: if (window.history.pushState) { const newURL = new URL(window.location.href); newURL.search = '?myNewUrlQuery=1'; window.history.pushState({ path: newURL.href }, '', newURL.href); }Ragamuffin
In Firefox, you are required to use a fully-qualified URL. Path only will generate a security error.Hanleigh
You should also add window.location.hash add the end of the string for newurl, otherwise you will have the query applied but you might overwrite an existing hash value.Acidulous
Would it also possible to have some react state change or triggere a useEffect if I change the url like this?Unusual
P
69

Old question, modern answer to help future devs; using the URL interface:

const url = new URL(window.location.href);
url.searchParams.set('key', value);
window.history.pushState(null, '', url.toString());

This also ensures you only change the specified query-parameter.

Pointtopoint answered 5/1, 2022 at 10:39 Comment(3)
yeah . you are right. i said wrong.Birddog
I think you need to use new URL(window.location.href) to get the full URL string.Overstrung
Well, the first parameter to new URL() can be an object as long as it has a stringifier, but you are right @CliftonLabrum, that would be cleaner and clearer. I've updated the answer.Pointtopoint
S
67

I want to improve Fabio's answer and create a function which adds custom key to the URL string without reloading the page.

 function insertUrlParam(key, value) {
        if (history.pushState) {
            let searchParams = new URLSearchParams(window.location.search);
            searchParams.set(key, value);
            let newurl = window.location.protocol + "//" + window.location.host + window.location.pathname + '?' + searchParams.toString();
            window.history.pushState({path: newurl}, '', newurl);
        }
    }

// to remove the specific key
export function removeUrlParameter(paramKey) {
  const url = window.location.href
  console.log("url", url)
  var r = new URL(url)
  r.searchParams.delete(paramKey)
  const newUrl = r.href
  console.log("r.href", newUrl)
  window.history.pushState({ path: newUrl }, '', newUrl)
}
Sledgehammer answered 26/12, 2018 at 8:55 Comment(2)
This is the exact answer to question. ThanksEntryway
brilliant, thanks for this!Backflow
S
13

If we simply want to update the query parameter without touching other parts of URL, there is no need to build the URL again. This is what I use:

const addQueryParam = (key, value) => {
  const url = new URL(window.location.href);
  url.searchParams.set(key, value);
  window.history.pushState({}, '', url.toString());
};

const getQueryParam = (key) => {
  const url = new URL(window.location.href);
  return url.searchParams.get(key) || '';
};
Samaniego answered 7/8, 2021 at 10:34 Comment(0)
C
7

I've used the following JavaScript library with great success:

https://github.com/balupton/jquery-history

It supports the HTML5 history API as well as a fallback method (using #) for older browsers.

This library is essentially a polyfill around `history.pushState'.

Citizen answered 10/6, 2012 at 16:12 Comment(5)
awesome! did you test it with all the browsers??Beggary
My implementation was tested in IE7+, Firefox 3.6+, Safari 5 and Chrome 16+. It probably works with other browsers too, but I haven't had any complaints in the several systems deployed using it.Citizen
great. so right now.. simply putting a # instead of a & when writing to window.location.href works for me. in that it doesn't reload the page. i am sure it will break once i test it in IE.. at which point i'll go with the library you suggested. thanksBeggary
The # method is a good one in that it has very wide browser support. Things can get complicated when you need to include that information in requests to the server, as the # part of the URL isn't sent by the browser. There are ways round that, which includes the library I referenced. In addition, using the HTML5 history API helps make your URLs shorter overall and requires less client side work to restore the state.Citizen
The # method is also problematic for social media sharing; Twitter, Facebook, and possibly others don't play well with anchor tags when creating previews or links back to the share.Vowell
S
7

Building off of Fabio's answer, I created two functions that will probably be useful for anyone stumbling upon this question. With these two functions, you can call insertParam() with a key and value as an argument. It will either add the URL parameter or, if a query param already exists with the same key, it will change that parameter to the new value:

//function to remove query params from a URL
function removeURLParameter(url, parameter) {
    //better to use l.search if you have a location/link object
    var urlparts= url.split('?');   
    if (urlparts.length>=2) {

        var prefix= encodeURIComponent(parameter)+'=';
        var pars= urlparts[1].split(/[&;]/g);

        //reverse iteration as may be destructive
        for (var i= pars.length; i-- > 0;) {    
            //idiom for string.startsWith
            if (pars[i].lastIndexOf(prefix, 0) !== -1) {  
                pars.splice(i, 1);
            }
        }

        url= urlparts[0] + (pars.length > 0 ? '?' + pars.join('&') : "");
        return url;
    } else {
        return url;
    }
}

//function to add/update query params
function insertParam(key, value) {
    if (history.pushState) {
        // var newurl = window.location.protocol + "//" + window.location.host + search.pathname + '?myNewUrlQuery=1';
        var currentUrlWithOutHash = window.location.origin + window.location.pathname + window.location.search;
        var hash = window.location.hash
        //remove any param for the same key
        var currentUrlWithOutHash = removeURLParameter(currentUrlWithOutHash, key);

        //figure out if we need to add the param with a ? or a &
        var queryStart;
        if(currentUrlWithOutHash.indexOf('?') !== -1){
            queryStart = '&';
        } else {
            queryStart = '?';
        }

        var newurl = currentUrlWithOutHash + queryStart + key + '=' + value + hash
        window.history.pushState({path:newurl},'',newurl);
    }
}
Sordino answered 20/2, 2018 at 16:0 Comment(4)
how your functions are better than new URLSearchParams() with its native methods as set and delete? in either cases we must to put it to the history with history.pushState()Haemocyte
new URLSearchParams() is not supported by any versions of IESordino
Thank you @Sordino this worked perfectly for me and was exactly what I was looking forLecher
...and IE is not supported by anyone.Cockleshell
I
4

Since everyone answering this seems to forget the hash, I want to add the code I'm using to keep all URL parameters:

const urlParams = new URLSearchParams(window.location.search);

/// Change some part of the URL params

if (history.pushState) {
  const newurl =
    window.location.protocol +
    "//" +
    window.location.host +
    window.location.pathname +
    "?" +
    urlParams.toString() +
    window.location.hash;
  window.history.replaceState({ path: newurl }, "", newurl);
} else {
  window.location.search = urlParams.toString();
}
Information answered 12/4, 2021 at 8:43 Comment(1)
pretty sure most answers aren't forgetting the hash, they are modifying an existing URL object exactly so that it preserves everythingShaduf
K
0

Then the history API is exactly what you are looking for. If you wish to support legacy browsers as well, then look for a library that falls back on manipulating the URL's hash tag if the browser doesn't provide the history API.

Khichabia answered 10/6, 2012 at 15:57 Comment(0)
P
0

I thought I'd add a bit to Fabio and Aram's answers. I thought I might sometimes like to preserve the hash in the url. But usually not, so I set that parameter to default to false.

replaceState still does not set the page title on Chrome. So I added a couple lines to change the title, if one is provided.

function insertUrlParam(key, value, title = '', preserve_hash = false) {
    if (history.pushState) {
        let searchParams = new URLSearchParams(window.location.search);
        searchParams.set(key, value);
        let newurl = window.location.protocol + "//" + window.location.host + window.location.pathname
            + '?' + searchParams.toString();
        if(preserve_hash) newurl = newurl + window.location.hash;
        let oldTitle = document.title;
        if(title !== '') {
            window.history.replaceState({path: newurl}, title, newurl);
            if(document.title !== title) { // fallback if above doesn't work
                document.title = title;
            }
        } else { // in case browsers ever clear titles set with empty string
            window.history.replaceState({path: newurl}, oldTitle, newurl);
        }
    }
}
Philharmonic answered 2/8, 2021 at 3:29 Comment(0)
P
0

I've made a custom hook to update query params without reloading the page. This is in typescript you can convert into javascript as well. react router 4 friendly!

import React, { useState } from "react";

const useSearchQuery = <T = string,>(queryKey: string, defaultValue?: T) => {
  const searchParams = new URLSearchParams(window.location.search);
  const [queryValue, setQueryValue] = useState<T>(
    (searchParams.get(queryKey) ?? defaultValue) as T
  );

  const setSearchParams = (value: T) => {
    if (value === queryValue) return;
    if (value) {
      searchParams.set(queryKey, value as string);
      setQueryValue(value);
    } else {
      searchParams.delete(queryKey);
      if (defaultValue) setQueryValue(defaultValue);
    }
    const newUrl = [window.location.pathname, searchParams.toString()]
      .filter(Boolean)
      .join("?");
    window.history.replaceState(null, "", newUrl);
  };

  return [queryValue, setSearchParams] as const;
};

export default useSearchQuery;

Hope this helps!

Parsley answered 1/11, 2023 at 12:8 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.