url-change-emitter
v1.0.0
Published
Lightweight URL change listener for vanilla JS & React apps (history, popstate, hashchange).
Maintainers
Readme
url-change-emitter
Lightweight URL change emitter for browser apps. It patches history.pushState and history.replaceState, listens to popstate and hashchange, then emits one consistent window event:
window.addEventListener("urlchange", (e) => {
// e: CustomEvent<UrlChangeDetail>
console.log(e.detail.url, e.detail.method);
});- Zero deps. Tree-shakable.
- Tiny surface area: one "attach" function and one "dispatch" helper.
- TypeScript types included.
Install
# pick one
pnpm add url-change-emitter
npm i url-change-emitter
yarn add url-change-emitterQuick start
Vanilla JS/TS
import { createUrlChangeListener } from "url-change-emitter";
const offPatch = createUrlChangeListener(); // patches history + adds listeners
// const offPatch2 = createUrlChangeListener(); // multiple calls are safe but not recommended
function onUrlChange(e: CustomEvent<UrlChangeDetail>) {
const { url, method } = e.detail;
console.log("[urlchange]", method, "->", url);
}
window.addEventListener("urlchange", onUrlChange);
// later, clean up
window.removeEventListener("urlchange", onUrlChange);
offPatch(); // unpatches when the last subscriber detachesReact/Nextjs
hook
import { useEffect, useState } from "react";
import { createUrlChangeListener, type UrlChangeEvent, type UrlChangeDetail } from "url-change-emitter";
export function useUrlChange() {
const [eventData, setEventData] = useState<UrlChangeDetail | null>(null);
useEffect(() => {
// Start listening for changes
const off = createUrlChangeListener();
const handler = (e: Event) => {
const detail = (e as UrlChangeEvent).detail;
setEventData(detail);
};
window.addEventListener("urlchange", handler);
return () => {
off(); // stop internal emitter
window.removeEventListener("urlchange", handler);
};
}, []);
return eventData;
}Types
export type UrlChangeDetail = {
url: string;
method: "pushState" | "replaceState" | "popstate" | "hashchange";
};
export type UrlChangeEvent = CustomEvent<UrlChangeDetail>;Functions
createUrlChangeListener(): () => void
Patches History APIs and attaches internal listeners to emiturlchange.
Returns anoff()function. When all callers have calledoff(), the originalhistorymethods are restored and listeners are removed (ref-counted).dispatchUrlChange(method: UrlChangeDetail["method"]): void
Manually dispatches aurlchangewith the currentlocation.href.
No-op if the URL is identical to the last dispatched one. Useful for emitting an initial value or in tests.
Why this works
pushStateandreplaceStatedo not fire events by default. We wrap them, call the originals, and then emit a single, consistenturlchange.- We also listen to
popstate(back/forward) andhashchange. - Duplicate events are prevented by remembering the last seen
location.href.
License
MIT
