UNPKG

mwouter

Version:

A minimalistic routing for React and Preact, Memory Edition. Nothing extra, just HOOKS.

143 lines (138 loc) 4.9 kB
'use strict'; 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;