This package has been created to cope with the fact that there's no native way in JavaScript of listening to window.location changes. There is the hashchange event and even the popstate event, but the former listens only to the hash and the latter only works when using the history (i.e. going backwards or forwards). There are alternatives available in most routing packages (e.g. React Router), however this may often be too heavyweight, and/or the functionality may be required in a place that makes calling these packages impractical.
The intention with this particular library is to give a simple, fast way of listening to location changes with as few dependencies as possible (hopefully none).
Install the dependency with one of the following commands:
yarn add @martindoyleuk/location-listener
npm install @martindoyleuk/location-listener
All code is written in Typescript and all types are exposed in the main library.
import { listenToLocation } from '@martindoyleuk/location-listener';
const unsubscribe = listenToLocation(
({ oldValues, newValues, changedProperties }) => {
console.log(`Changed properties were "${changedProperties.join('", "')}"`);
console.log('Old location values:', oldValues);
console.log('New location values:', newValues);
},
);
unsubscribe();
import { useLocationListener } from '@martindoyleuk/location-listener';
import React, { useEffect } from 'react';
export const MyComponent = () => {
const { hash, search, pathname } = useLocationListener();
useEffect(() => {
console.log(`Current location hash value is ${hash}`);
console.log(`Current location search value is ${search}`);
console.log(`Current location pathname value is ${pathname}`);
}, [hash, search, pathname]);
};
callback ({ oldValues, newValues, changedProperties }) => void
The listener callback function"oldValues" { href, search, hash, pathname }
The previous location values"href" string
Thelocation.href
value from the previous location"search" string
Thelocation.search
value from the previous location"hash" string
Thelocation.hash
value from the previous location"pathname" string
Thelocation.pathname
value from the previous location
"newValues" { href, search, hash, pathname }
The new/current location values"href" string
Thelocation.href
value from the updated location"search" string
Thelocation.search
value from the updated location"hash" string
Thelocation.hash
value from the updated location"pathname" string
Thelocation.pathname
value from the updated location
"changedProperties" string[]
The names of the properties that changed
"options" { ignoreHashChange, ignorePathnameChange, ignoreSearchChange, immediatelySendValues }
Optional options object"ignoreHashChange" [boolean]
Do not fire when only thehash
property changed"ignorePathnameChange" [boolean]
Do not fire when only thepathname
property changed"ignoreSearchChange" [boolean]
Do not fire when only thesearch
property changed"immediatelySendValues" [boolean]
Immediately invoke the callback as soon as the listener is registered
"unsubscribe" () => void
Can be invoked to remove the listener and stop it being called when the location changes
"options" { shouldLogChanges }
Optional options object"shouldLogChanges" [boolean]
Whether to store location changes"maxChanges" [number]
Maximum number of changes to store (default100
)
"locationData" { hash, href, pathname, search, changes }
The current location data, updated automatically"hash" string
The current value of thelocation.hash
parameter"href" string
The current value of thelocation.href
parameter"pathname" string
The current value of thelocation.pathname
parameter"search" string
The current value of thelocation.search
parameter"changes" { datetime, href }[]
Potentially useful for debugging purposes (only populated ifshouldLogChanges
option is specified, for performance reasons)"datetime" string
When this value was observed (ISO datetime format)"href" string
Thelocation.href
value at the time of the observation
- How does it work?
- Under the hood, there is a simple
window.setTimeout
call that (after50ms
) checks the location object and then notifies all listeners if it changes. It then re-sets up the timeout again. Each callback is fired in the order it was added, and asynchronously via a call tosetTimeout
with a0ms
delay.
- Under the hood, there is a simple
- Why
setTimeout
oversetInterval
?- The initial concept was based on using
setInterval
, however there is a negligible risk with many listeners that they could overrun the specified interval. For this reason, it was decided to retrigger with another50ms
delay after the previous batch of listeners' callbacks were fired. As an added bonus, if, for some reason, something were to fatally error inside the main thread, thesetTimeout
would never be called, protecting the browser from infinite calling of bad code.
- The initial concept was based on using
- What are the performance costs?
- Detailed performance analysis has not been run, however the code has been set up to ensure singleton usage of the package, meaning there will only ever be one listener on the page at once. The expected impact of a single
setTimeout
is expected to be negligible due to the way the event loop works, specifically that the interval function will not trigger while other work is ongoing.
- Detailed performance analysis has not been run, however the code has been set up to ensure singleton usage of the package, meaning there will only ever be one listener on the page at once. The expected impact of a single
- Are there any further improvements planned?
- No. Issues and feature requests will always be considered, but at the moment this is considered feature-complete.