mwouter
Version:
A minimalistic routing for React and Preact, Memory Edition. Nothing extra, just HOOKS.
143 lines (138 loc) • 4.9 kB
JavaScript
;
Object.defineProperty(exports, '__esModule', { value: true });
var react = require('react');
var locationHook = ({ base = "" } = {}) => {
const [{ path, search }, update] = react.useState(() => ({
path: currentPathname(base),
search: memoryHistory.state.search,
}));
const prevHash = react.useRef(path + search);
react.useEffect(() => {
// this function checks if the memoryHistory.state has been changed since the
// last render and updates the state only when needed.
// unfortunately, we can't rely on `path` value here, since it can be stale,
// that's why we store the last pathname in a ref.
const checkForUpdates = () => {
const pathname = currentPathname(base);
const search = memoryHistory.state.search;
const hash = pathname + search;
if (prevHash.current !== hash) {
prevHash.current = hash;
update({ path: pathname, search });
}
};
memoryHistory.addListener(checkForUpdates);
// it's possible that an update has occurred between render and the effect handler,
// so we run additional check on mount to catch these updates.
checkForUpdates();
return () => { memoryHistory.removeListener(checkForUpdates); };
}, [base, update]);
// the 2nd argument of the `useLocation` return value is a function
// that allows to perform a navigation.
//
// the function reference should stay the same between re-renders, so that
// it can be passed down as an element prop without any performance concerns.
const navigate = react.useCallback((to, { replace = false } = {}) => memoryHistory[replace ? eventReplaceState : eventPushState](null, "",
// handle nested routers and absolute paths
to[0] === "~" ? to.slice(1) : base + to), [base, update]);
return [path, navigate];
};
const currentPathname = (base, path = memoryHistory.state.pathname || '/') => !path.toLowerCase().indexOf(base.toLowerCase())
? path.slice(base.length) || "/"
: "~" + path;
const eventPopstate = "popstate";
const eventPushState = "pushState";
const eventReplaceState = "replaceState";
function createEvents() {
let handlers = [];
return {
get length() {
return handlers.length;
},
addListener(fn) {
handlers.push(fn);
},
removeListener(fn) {
const index = handlers.findIndex((f) => f === fn);
if (index !== -1) {
handlers.splice(index, 1);
}
},
call(type, state) {
handlers.forEach((fn) => fn && fn(type, state));
}
};
}
class MemoryHistory {
entries = [];
listeners = createEvents();
/**
* Gets the number of entries currently stored in the history.
*/
get length() {
return this.entries.length;
}
/**
* Pops the most recent history entry from the top of the stack and issues an
* 'onpopstate' event.
*/
back() {
const poppedState = this.popState();
this.listeners.call(eventPopstate, poppedState);
}
/**
* Removes the topmost history entry from the stack.
*
* Note that this does not notify any onpopstate listeners. This is for
* internal modification of the history stack. For backwards navigation use
* the back() method.
*/
popState() {
return this.entries.pop();
}
forward() {
throw new Error("Not implemented");
}
go(delta) {
throw new Error("Not implemented");
}
get state() {
if (this.entries.length === 0) {
return new URL(`http://localhost/`);
}
return this.entries[this.entries.length - 1] || new URL(`http://localhost/`);
}
pushState(state, title, url) {
if (!state) {
state = new URL(`http://localhost${url || '/'}`);
}
this.entries.push(state);
this.listeners.call(eventPushState, state);
}
replaceState(state, title, url) {
if (!state) {
state = new URL(`http://localhost${url || '/'}`);
}
if (this.entries.length === 0) {
this.entries.push(state);
}
else {
this.entries[this.entries.length - 1] = state;
}
this.listeners.call(eventReplaceState, state);
}
addListener(callback) {
this.listeners.addListener(callback);
return this;
}
removeListener(callback) {
this.listeners.removeListener(callback);
return this;
}
}
const memoryHistory = new MemoryHistory();
exports["default"] = locationHook;
exports.eventPopstate = eventPopstate;
exports.eventPushState = eventPushState;
exports.eventReplaceState = eventReplaceState;
exports.memoryHistory = memoryHistory;