250

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';
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. Commented Jun 10, 2012 at 15:50
  • possible duplicate of how to manipulate the URL with javascript/jquery? Commented Jun 10, 2012 at 15:52
  • possible duplicate of How does GitHub change the URL but not the reload? Commented Jun 10, 2012 at 15:53
  • 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 of Commented Jun 10, 2012 at 15:54
  • Note that window.location.hash gives you access to the hash directly, and that's supported pretty much everywhere. Commented Jun 10, 2012 at 17:26

11 Answers 11

318

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

Sign up to request clarification or add additional context in comments.

11 Comments

I believe you can shorten window.location.protocol + '//' + window.location.host to just: window.location.origin.
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.
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.
USE history.replaceState() otherwise you need to click BACK button twice in your browser
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); }
|
116

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.

4 Comments

yeah . you are right. i said wrong.
I think you need to use new URL(window.location.href) to get the full URL string.
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.
document.URL is also available as a read-only getter developer.mozilla.org/en-US/docs/Web/API/Document/URL in place of window.location.href.
70

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)
}

Comments

18

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) || '';
};

Comments

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'.

5 Comments

awesome! did you test it with all the browsers??
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.
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. thanks
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.
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.
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);
    }
}

4 Comments

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()
new URLSearchParams() is not supported by any versions of IE
Thank you @jmona789 this worked perfectly for me and was exactly what I was looking for
...and IE is not supported by anyone.
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();
}

1 Comment

pretty sure most answers aren't forgetting the hash, they are modifying an existing URL object exactly so that it preserves everything
1

Clean and simple, use replaceState and rebuild your URL.

if (window.history.replaceState) {
  const newURL = window.location.origin + window.location.pathname + '?abc=1';
  window.history.replaceState(null, '', newURL);
}

You can add window.location.search and window.location.hash as well to it if you need to preserve that information.

Comments

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.

Comments

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);
        }
    }
}

Comments

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!

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.