Trigger function when URL changes - vanilla JS

I am working with Single Page Application, when URL changes without reloading the page. That means my script is available all the time for the page.

I want to trigger a function, when URL changes, using only vanilla JS
I used this solution

var pushState = history.pushState;
history.pushState = function() {
	pushState.apply(history, arguments);
	functionToBeCalled();
};

But I get errror:

Uncaught RangeError: Maximum call stack size exceeded
at History.history.pushState

What is wrong with my approach? Is there any other option?

In js all functions are methods, what this means is that each function needs to be associated with an object(which is the owner of function). If you are familiar with OOPs then word ‘method’ means the same as method in OOP. In terms of C++, method is same as member function. Now apply is used to call a function (which is method of an object say A) with another object (say B) as its owner.

Let us understand it with following piece of code :

var person = {
  fullName: function() {
    return this.firstName + " " + this.lastName;
  }
}
var person1 = {
  firstName: "Mary",
  lastName: "Doe"
}
person.fullName.apply(person1);  // Will return "Mary Doe"

Above code has been taken from
https://www.w3schools.com/js/js_function_apply.asp

Now in above example although fullName function was associated with person object, we are calling it as if it were associated with person1 object. So we get Mary Doe as the return value, otherwise it would have been “undefined undefined”.

Now let’s analyse the code you wrote. You are creating a variable pushState( this is a method associated with global object). You are defining history.pushState and in that you are using apply to call pushState(associated with global object) with history object as it’s owner. This invokes pushState method associated with history object. But since you have redefined the pushState method on history object, js engine tries to call this redefined method. And in the very first line of method, you are using apply, which invokes the pushState method of history object again. So in this way you invoke the same function again and again and go into an infinite loop. So the call stack exceeds.

If you did not get the above part clearly, try to execute below code

var pushState = history.pushState;
history.pushState = function() {
    console.log("Function called :")
	pushState.apply(history, arguments);
	functionToBeCalled();
};

You will see that ‘function called’ is logged again and again, this is because you are trying to call the function(pushState) in it’s definition only. This is like recursion but there is not base case provided, so we keep calling the same function again and again.

var pushState = history.pushState: so you are saying these are the same thing.

history.pushState = function() {
	pushState.apply(history, arguments);
	functionToBeCalled();
};

So you’re now trying to overwrite how the pushState function works (note that even if this worked, overwriting how browser APIs work is an Extremely Bad Idea). And the way you’ve defined it is when the function is called it immediately executes the same function. When that executes, it immediately executes the same function. And so on, until you either run out of memory or JS panics and kills the execution.

function pushStateAndDoSomething({ state, title, url = null} , callback) {
  history.pushState(state, title, url);
  callback();
}

@DanCouper thank you very much for the feedback.

How did you come up with the function you posted?
What would be state in the function? By title you mean page title?
Would you replace my function with the code you have posted?

@nkwatra Thanks a lot for taking time to explain the principles of the approach.
I followed your hint and added

console.log("Function called :")

but interestingly it’s triggered only once after new page loads.