UNPKG

wouter-preact

Version:

Minimalist-friendly ~1.5KB router for Preact

91 lines (76 loc) 3.13 kB
import { useSyncExternalStore } from "./react-deps.js"; /** * History API docs @see https://developer.mozilla.org/en-US/docs/Web/API/History */ const eventPopstate = "popstate"; const eventPushState = "pushState"; const eventReplaceState = "replaceState"; const eventHashchange = "hashchange"; const events = [ eventPopstate, eventPushState, eventReplaceState, eventHashchange, ]; const subscribeToLocationUpdates = (callback) => { for (const event of events) { addEventListener(event, callback); } return () => { for (const event of events) { removeEventListener(event, callback); } }; }; export const useLocationProperty = (fn, ssrFn) => useSyncExternalStore(subscribeToLocationUpdates, fn, ssrFn); const currentSearch = () => location.search; export const useSearch = ({ ssrSearch } = {}) => useLocationProperty( currentSearch, // != null checks for both null and undefined, but allows empty string "" // This allows proper hydration: server renders with ssrSearch="?foo", // client hydrates with just <Router /> and reads from location.search ssrSearch != null ? () => ssrSearch : currentSearch ); const currentPathname = () => location.pathname; export const usePathname = ({ ssrPath } = {}) => useLocationProperty( currentPathname, // != null checks for both null and undefined, but allows empty string "" // This allows proper hydration: server renders with ssrPath="/foo", // client hydrates with just <Router /> and reads from location.pathname ssrPath != null ? () => ssrPath : currentPathname ); const currentHistoryState = () => history.state; export const useHistoryState = () => useLocationProperty(currentHistoryState, () => null); export const navigate = (to, { replace = false, state = null } = {}) => history[replace ? eventReplaceState : eventPushState](state, "", to); // the 2nd argument of the `useBrowserLocation` return value is a function // that allows to perform a navigation. export const useBrowserLocation = (opts = {}) => [usePathname(opts), navigate]; const patchKey = Symbol.for("wouter_v3"); // While History API does have `popstate` event, the only // proper way to listen to changes via `push/replaceState` // is to monkey-patch these methods. // // See https://stackoverflow.com/a/4585031 if (typeof history !== "undefined" && typeof window[patchKey] === "undefined") { for (const type of [eventPushState, eventReplaceState]) { const original = history[type]; // TODO: we should be using unstable_batchedUpdates to avoid multiple re-renders, // however that will require an additional peer dependency on react-dom. // See: https://github.com/reactwg/react-18/discussions/86#discussioncomment-1567149 history[type] = function () { const result = original.apply(this, arguments); const event = new Event(type); event.arguments = arguments; dispatchEvent(event); return result; }; } // patch history object only once // See: https://github.com/molefrog/wouter/issues/167 Object.defineProperty(window, patchKey, { value: true }); }