react-router-dom.development.js 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984
  1. /**
  2. * React Router DOM v6.8.1
  3. *
  4. * Copyright (c) Remix Software Inc.
  5. *
  6. * This source code is licensed under the MIT license found in the
  7. * LICENSE.md file in the root directory of this source tree.
  8. *
  9. * @license MIT
  10. */
  11. import * as React from 'react';
  12. import { UNSAFE_enhanceManualRouteObjects, Router, useHref, useResolvedPath, useLocation, UNSAFE_DataRouterStateContext, UNSAFE_NavigationContext, useNavigate, createPath, UNSAFE_RouteContext, useMatches, useNavigation, unstable_useBlocker, UNSAFE_DataRouterContext } from 'react-router';
  13. export { AbortedDeferredError, Await, MemoryRouter, Navigate, NavigationType, Outlet, Route, Router, RouterProvider, Routes, UNSAFE_DataRouterContext, UNSAFE_DataRouterStateContext, UNSAFE_LocationContext, UNSAFE_NavigationContext, UNSAFE_RouteContext, UNSAFE_enhanceManualRouteObjects, createMemoryRouter, createPath, createRoutesFromChildren, createRoutesFromElements, defer, generatePath, isRouteErrorResponse, json, matchPath, matchRoutes, parsePath, redirect, renderMatches, resolvePath, unstable_useBlocker, useActionData, useAsyncError, useAsyncValue, useHref, useInRouterContext, useLoaderData, useLocation, useMatch, useMatches, useNavigate, useNavigation, useNavigationType, useOutlet, useOutletContext, useParams, useResolvedPath, useRevalidator, useRouteError, useRouteLoaderData, useRoutes } from 'react-router';
  14. import { createRouter, createBrowserHistory, createHashHistory, ErrorResponse, invariant, joinPaths } from '@remix-run/router';
  15. const defaultMethod = "get";
  16. const defaultEncType = "application/x-www-form-urlencoded";
  17. function isHtmlElement(object) {
  18. return object != null && typeof object.tagName === "string";
  19. }
  20. function isButtonElement(object) {
  21. return isHtmlElement(object) && object.tagName.toLowerCase() === "button";
  22. }
  23. function isFormElement(object) {
  24. return isHtmlElement(object) && object.tagName.toLowerCase() === "form";
  25. }
  26. function isInputElement(object) {
  27. return isHtmlElement(object) && object.tagName.toLowerCase() === "input";
  28. }
  29. function isModifiedEvent(event) {
  30. return !!(event.metaKey || event.altKey || event.ctrlKey || event.shiftKey);
  31. }
  32. function shouldProcessLinkClick(event, target) {
  33. return event.button === 0 && ( // Ignore everything but left clicks
  34. !target || target === "_self") && // Let browser handle "target=_blank" etc.
  35. !isModifiedEvent(event) // Ignore clicks with modifier keys
  36. ;
  37. }
  38. /**
  39. * Creates a URLSearchParams object using the given initializer.
  40. *
  41. * This is identical to `new URLSearchParams(init)` except it also
  42. * supports arrays as values in the object form of the initializer
  43. * instead of just strings. This is convenient when you need multiple
  44. * values for a given key, but don't want to use an array initializer.
  45. *
  46. * For example, instead of:
  47. *
  48. * let searchParams = new URLSearchParams([
  49. * ['sort', 'name'],
  50. * ['sort', 'price']
  51. * ]);
  52. *
  53. * you can do:
  54. *
  55. * let searchParams = createSearchParams({
  56. * sort: ['name', 'price']
  57. * });
  58. */
  59. function createSearchParams(init = "") {
  60. return new URLSearchParams(typeof init === "string" || Array.isArray(init) || init instanceof URLSearchParams ? init : Object.keys(init).reduce((memo, key) => {
  61. let value = init[key];
  62. return memo.concat(Array.isArray(value) ? value.map(v => [key, v]) : [[key, value]]);
  63. }, []));
  64. }
  65. function getSearchParamsForLocation(locationSearch, defaultSearchParams) {
  66. let searchParams = createSearchParams(locationSearch);
  67. if (defaultSearchParams) {
  68. for (let key of defaultSearchParams.keys()) {
  69. if (!searchParams.has(key)) {
  70. defaultSearchParams.getAll(key).forEach(value => {
  71. searchParams.append(key, value);
  72. });
  73. }
  74. }
  75. }
  76. return searchParams;
  77. }
  78. function getFormSubmissionInfo(target, defaultAction, options) {
  79. let method;
  80. let action;
  81. let encType;
  82. let formData;
  83. if (isFormElement(target)) {
  84. let submissionTrigger = options.submissionTrigger;
  85. method = options.method || target.getAttribute("method") || defaultMethod;
  86. action = options.action || target.getAttribute("action") || defaultAction;
  87. encType = options.encType || target.getAttribute("enctype") || defaultEncType;
  88. formData = new FormData(target);
  89. if (submissionTrigger && submissionTrigger.name) {
  90. formData.append(submissionTrigger.name, submissionTrigger.value);
  91. }
  92. } else if (isButtonElement(target) || isInputElement(target) && (target.type === "submit" || target.type === "image")) {
  93. let form = target.form;
  94. if (form == null) {
  95. throw new Error(`Cannot submit a <button> or <input type="submit"> without a <form>`);
  96. } // <button>/<input type="submit"> may override attributes of <form>
  97. method = options.method || target.getAttribute("formmethod") || form.getAttribute("method") || defaultMethod;
  98. action = options.action || target.getAttribute("formaction") || form.getAttribute("action") || defaultAction;
  99. encType = options.encType || target.getAttribute("formenctype") || form.getAttribute("enctype") || defaultEncType;
  100. formData = new FormData(form); // Include name + value from a <button>, appending in case the button name
  101. // matches an existing input name
  102. if (target.name) {
  103. formData.append(target.name, target.value);
  104. }
  105. } else if (isHtmlElement(target)) {
  106. throw new Error(`Cannot submit element that is not <form>, <button>, or ` + `<input type="submit|image">`);
  107. } else {
  108. method = options.method || defaultMethod;
  109. action = options.action || defaultAction;
  110. encType = options.encType || defaultEncType;
  111. if (target instanceof FormData) {
  112. formData = target;
  113. } else {
  114. formData = new FormData();
  115. if (target instanceof URLSearchParams) {
  116. for (let [name, value] of target) {
  117. formData.append(name, value);
  118. }
  119. } else if (target != null) {
  120. for (let name of Object.keys(target)) {
  121. formData.append(name, target[name]);
  122. }
  123. }
  124. }
  125. }
  126. let {
  127. protocol,
  128. host
  129. } = window.location;
  130. let url = new URL(action, `${protocol}//${host}`);
  131. return {
  132. url,
  133. method: method.toLowerCase(),
  134. encType,
  135. formData
  136. };
  137. }
  138. /**
  139. * NOTE: If you refactor this to split up the modules into separate files,
  140. * you'll need to update the rollup config for react-router-dom-v5-compat.
  141. */
  142. ////////////////////////////////////////////////////////////////////////////////
  143. //#region Routers
  144. ////////////////////////////////////////////////////////////////////////////////
  145. function createBrowserRouter(routes, opts) {
  146. return createRouter({
  147. basename: opts?.basename,
  148. history: createBrowserHistory({
  149. window: opts?.window
  150. }),
  151. hydrationData: opts?.hydrationData || parseHydrationData(),
  152. routes: UNSAFE_enhanceManualRouteObjects(routes)
  153. }).initialize();
  154. }
  155. function createHashRouter(routes, opts) {
  156. return createRouter({
  157. basename: opts?.basename,
  158. history: createHashHistory({
  159. window: opts?.window
  160. }),
  161. hydrationData: opts?.hydrationData || parseHydrationData(),
  162. routes: UNSAFE_enhanceManualRouteObjects(routes)
  163. }).initialize();
  164. }
  165. function parseHydrationData() {
  166. let state = window?.__staticRouterHydrationData;
  167. if (state && state.errors) {
  168. state = { ...state,
  169. errors: deserializeErrors(state.errors)
  170. };
  171. }
  172. return state;
  173. }
  174. function deserializeErrors(errors) {
  175. if (!errors) return null;
  176. let entries = Object.entries(errors);
  177. let serialized = {};
  178. for (let [key, val] of entries) {
  179. // Hey you! If you change this, please change the corresponding logic in
  180. // serializeErrors in react-router-dom/server.tsx :)
  181. if (val && val.__type === "RouteErrorResponse") {
  182. serialized[key] = new ErrorResponse(val.status, val.statusText, val.data, val.internal === true);
  183. } else if (val && val.__type === "Error") {
  184. let error = new Error(val.message); // Wipe away the client-side stack trace. Nothing to fill it in with
  185. // because we don't serialize SSR stack traces for security reasons
  186. error.stack = "";
  187. serialized[key] = error;
  188. } else {
  189. serialized[key] = val;
  190. }
  191. }
  192. return serialized;
  193. } //#endregion
  194. ////////////////////////////////////////////////////////////////////////////////
  195. //#region Components
  196. ////////////////////////////////////////////////////////////////////////////////
  197. /**
  198. * A `<Router>` for use in web browsers. Provides the cleanest URLs.
  199. */
  200. function BrowserRouter({
  201. basename,
  202. children,
  203. window
  204. }) {
  205. let historyRef = React.useRef();
  206. if (historyRef.current == null) {
  207. historyRef.current = createBrowserHistory({
  208. window,
  209. v5Compat: true
  210. });
  211. }
  212. let history = historyRef.current;
  213. let [state, setState] = React.useState({
  214. action: history.action,
  215. location: history.location
  216. });
  217. React.useLayoutEffect(() => history.listen(setState), [history]);
  218. return /*#__PURE__*/React.createElement(Router, {
  219. basename: basename,
  220. children: children,
  221. location: state.location,
  222. navigationType: state.action,
  223. navigator: history
  224. });
  225. }
  226. /**
  227. * A `<Router>` for use in web browsers. Stores the location in the hash
  228. * portion of the URL so it is not sent to the server.
  229. */
  230. function HashRouter({
  231. basename,
  232. children,
  233. window
  234. }) {
  235. let historyRef = React.useRef();
  236. if (historyRef.current == null) {
  237. historyRef.current = createHashHistory({
  238. window,
  239. v5Compat: true
  240. });
  241. }
  242. let history = historyRef.current;
  243. let [state, setState] = React.useState({
  244. action: history.action,
  245. location: history.location
  246. });
  247. React.useLayoutEffect(() => history.listen(setState), [history]);
  248. return /*#__PURE__*/React.createElement(Router, {
  249. basename: basename,
  250. children: children,
  251. location: state.location,
  252. navigationType: state.action,
  253. navigator: history
  254. });
  255. }
  256. /**
  257. * A `<Router>` that accepts a pre-instantiated history object. It's important
  258. * to note that using your own history object is highly discouraged and may add
  259. * two versions of the history library to your bundles unless you use the same
  260. * version of the history library that React Router uses internally.
  261. */
  262. function HistoryRouter({
  263. basename,
  264. children,
  265. history
  266. }) {
  267. const [state, setState] = React.useState({
  268. action: history.action,
  269. location: history.location
  270. });
  271. React.useLayoutEffect(() => history.listen(setState), [history]);
  272. return /*#__PURE__*/React.createElement(Router, {
  273. basename: basename,
  274. children: children,
  275. location: state.location,
  276. navigationType: state.action,
  277. navigator: history
  278. });
  279. }
  280. {
  281. HistoryRouter.displayName = "unstable_HistoryRouter";
  282. }
  283. const isBrowser = typeof window !== "undefined" && typeof window.document !== "undefined" && typeof window.document.createElement !== "undefined";
  284. /**
  285. * The public API for rendering a history-aware <a>.
  286. */
  287. const Link = /*#__PURE__*/React.forwardRef(function LinkWithRef({
  288. onClick,
  289. relative,
  290. reloadDocument,
  291. replace,
  292. state,
  293. target,
  294. to,
  295. preventScrollReset,
  296. ...rest
  297. }, ref) {
  298. // Rendered into <a href> for absolute URLs
  299. let absoluteHref;
  300. let isExternal = false;
  301. if (isBrowser && typeof to === "string" && /^(?:[a-z][a-z0-9+.-]*:|\/\/)/i.test(to)) {
  302. absoluteHref = to;
  303. let currentUrl = new URL(window.location.href);
  304. let targetUrl = to.startsWith("//") ? new URL(currentUrl.protocol + to) : new URL(to);
  305. if (targetUrl.origin === currentUrl.origin) {
  306. // Strip the protocol/origin for same-origin absolute URLs
  307. to = targetUrl.pathname + targetUrl.search + targetUrl.hash;
  308. } else {
  309. isExternal = true;
  310. }
  311. } // Rendered into <a href> for relative URLs
  312. let href = useHref(to, {
  313. relative
  314. });
  315. let internalOnClick = useLinkClickHandler(to, {
  316. replace,
  317. state,
  318. target,
  319. preventScrollReset,
  320. relative
  321. });
  322. function handleClick(event) {
  323. if (onClick) onClick(event);
  324. if (!event.defaultPrevented) {
  325. internalOnClick(event);
  326. }
  327. }
  328. return (
  329. /*#__PURE__*/
  330. // eslint-disable-next-line jsx-a11y/anchor-has-content
  331. React.createElement("a", Object.assign({}, rest, {
  332. href: absoluteHref || href,
  333. onClick: isExternal || reloadDocument ? onClick : handleClick,
  334. ref: ref,
  335. target: target
  336. }))
  337. );
  338. });
  339. {
  340. Link.displayName = "Link";
  341. }
  342. /**
  343. * A <Link> wrapper that knows if it's "active" or not.
  344. */
  345. const NavLink = /*#__PURE__*/React.forwardRef(function NavLinkWithRef({
  346. "aria-current": ariaCurrentProp = "page",
  347. caseSensitive = false,
  348. className: classNameProp = "",
  349. end = false,
  350. style: styleProp,
  351. to,
  352. children,
  353. ...rest
  354. }, ref) {
  355. let path = useResolvedPath(to, {
  356. relative: rest.relative
  357. });
  358. let location = useLocation();
  359. let routerState = React.useContext(UNSAFE_DataRouterStateContext);
  360. let {
  361. navigator
  362. } = React.useContext(UNSAFE_NavigationContext);
  363. let toPathname = navigator.encodeLocation ? navigator.encodeLocation(path).pathname : path.pathname;
  364. let locationPathname = location.pathname;
  365. let nextLocationPathname = routerState && routerState.navigation && routerState.navigation.location ? routerState.navigation.location.pathname : null;
  366. if (!caseSensitive) {
  367. locationPathname = locationPathname.toLowerCase();
  368. nextLocationPathname = nextLocationPathname ? nextLocationPathname.toLowerCase() : null;
  369. toPathname = toPathname.toLowerCase();
  370. }
  371. let isActive = locationPathname === toPathname || !end && locationPathname.startsWith(toPathname) && locationPathname.charAt(toPathname.length) === "/";
  372. let isPending = nextLocationPathname != null && (nextLocationPathname === toPathname || !end && nextLocationPathname.startsWith(toPathname) && nextLocationPathname.charAt(toPathname.length) === "/");
  373. let ariaCurrent = isActive ? ariaCurrentProp : undefined;
  374. let className;
  375. if (typeof classNameProp === "function") {
  376. className = classNameProp({
  377. isActive,
  378. isPending
  379. });
  380. } else {
  381. // If the className prop is not a function, we use a default `active`
  382. // class for <NavLink />s that are active. In v5 `active` was the default
  383. // value for `activeClassName`, but we are removing that API and can still
  384. // use the old default behavior for a cleaner upgrade path and keep the
  385. // simple styling rules working as they currently do.
  386. className = [classNameProp, isActive ? "active" : null, isPending ? "pending" : null].filter(Boolean).join(" ");
  387. }
  388. let style = typeof styleProp === "function" ? styleProp({
  389. isActive,
  390. isPending
  391. }) : styleProp;
  392. return /*#__PURE__*/React.createElement(Link, Object.assign({}, rest, {
  393. "aria-current": ariaCurrent,
  394. className: className,
  395. ref: ref,
  396. style: style,
  397. to: to
  398. }), typeof children === "function" ? children({
  399. isActive,
  400. isPending
  401. }) : children);
  402. });
  403. {
  404. NavLink.displayName = "NavLink";
  405. }
  406. /**
  407. * A `@remix-run/router`-aware `<form>`. It behaves like a normal form except
  408. * that the interaction with the server is with `fetch` instead of new document
  409. * requests, allowing components to add nicer UX to the page as the form is
  410. * submitted and returns with data.
  411. */
  412. const Form = /*#__PURE__*/React.forwardRef((props, ref) => {
  413. return /*#__PURE__*/React.createElement(FormImpl, Object.assign({}, props, {
  414. ref: ref
  415. }));
  416. });
  417. {
  418. Form.displayName = "Form";
  419. }
  420. const FormImpl = /*#__PURE__*/React.forwardRef(({
  421. reloadDocument,
  422. replace,
  423. method: _method = defaultMethod,
  424. action,
  425. onSubmit,
  426. fetcherKey,
  427. routeId,
  428. relative,
  429. preventScrollReset,
  430. ...props
  431. }, forwardedRef) => {
  432. let submit = useSubmitImpl(fetcherKey, routeId);
  433. let formMethod = _method.toLowerCase() === "get" ? "get" : "post";
  434. let formAction = useFormAction(action, {
  435. relative
  436. });
  437. let submitHandler = event => {
  438. onSubmit && onSubmit(event);
  439. if (event.defaultPrevented) return;
  440. event.preventDefault();
  441. let submitter = event.nativeEvent.submitter;
  442. let submitMethod = submitter?.getAttribute("formmethod") || _method;
  443. submit(submitter || event.currentTarget, {
  444. method: submitMethod,
  445. replace,
  446. relative,
  447. preventScrollReset
  448. });
  449. };
  450. return /*#__PURE__*/React.createElement("form", Object.assign({
  451. ref: forwardedRef,
  452. method: formMethod,
  453. action: formAction,
  454. onSubmit: reloadDocument ? onSubmit : submitHandler
  455. }, props));
  456. });
  457. {
  458. FormImpl.displayName = "FormImpl";
  459. }
  460. /**
  461. * This component will emulate the browser's scroll restoration on location
  462. * changes.
  463. */
  464. function ScrollRestoration({
  465. getKey,
  466. storageKey
  467. }) {
  468. useScrollRestoration({
  469. getKey,
  470. storageKey
  471. });
  472. return null;
  473. }
  474. {
  475. ScrollRestoration.displayName = "ScrollRestoration";
  476. } //#endregion
  477. ////////////////////////////////////////////////////////////////////////////////
  478. //#region Hooks
  479. ////////////////////////////////////////////////////////////////////////////////
  480. var DataRouterHook;
  481. (function (DataRouterHook) {
  482. DataRouterHook["UseScrollRestoration"] = "useScrollRestoration";
  483. DataRouterHook["UseSubmitImpl"] = "useSubmitImpl";
  484. DataRouterHook["UseFetcher"] = "useFetcher";
  485. })(DataRouterHook || (DataRouterHook = {}));
  486. var DataRouterStateHook;
  487. (function (DataRouterStateHook) {
  488. DataRouterStateHook["UseFetchers"] = "useFetchers";
  489. DataRouterStateHook["UseScrollRestoration"] = "useScrollRestoration";
  490. })(DataRouterStateHook || (DataRouterStateHook = {}));
  491. function getDataRouterConsoleError(hookName) {
  492. return `${hookName} must be used within a data router. See https://reactrouter.com/routers/picking-a-router.`;
  493. }
  494. function useDataRouterContext(hookName) {
  495. let ctx = React.useContext(UNSAFE_DataRouterContext);
  496. !ctx ? invariant(false, getDataRouterConsoleError(hookName)) : void 0;
  497. return ctx;
  498. }
  499. function useDataRouterState(hookName) {
  500. let state = React.useContext(UNSAFE_DataRouterStateContext);
  501. !state ? invariant(false, getDataRouterConsoleError(hookName)) : void 0;
  502. return state;
  503. }
  504. /**
  505. * Handles the click behavior for router `<Link>` components. This is useful if
  506. * you need to create custom `<Link>` components with the same click behavior we
  507. * use in our exported `<Link>`.
  508. */
  509. function useLinkClickHandler(to, {
  510. target,
  511. replace: replaceProp,
  512. state,
  513. preventScrollReset,
  514. relative
  515. } = {}) {
  516. let navigate = useNavigate();
  517. let location = useLocation();
  518. let path = useResolvedPath(to, {
  519. relative
  520. });
  521. return React.useCallback(event => {
  522. if (shouldProcessLinkClick(event, target)) {
  523. event.preventDefault(); // If the URL hasn't changed, a regular <a> will do a replace instead of
  524. // a push, so do the same here unless the replace prop is explicitly set
  525. let replace = replaceProp !== undefined ? replaceProp : createPath(location) === createPath(path);
  526. navigate(to, {
  527. replace,
  528. state,
  529. preventScrollReset,
  530. relative
  531. });
  532. }
  533. }, [location, navigate, path, replaceProp, state, target, to, preventScrollReset, relative]);
  534. }
  535. /**
  536. * A convenient wrapper for reading and writing search parameters via the
  537. * URLSearchParams interface.
  538. */
  539. function useSearchParams(defaultInit) {
  540. warning(typeof URLSearchParams !== "undefined", `You cannot use the \`useSearchParams\` hook in a browser that does not ` + `support the URLSearchParams API. If you need to support Internet ` + `Explorer 11, we recommend you load a polyfill such as ` + `https://github.com/ungap/url-search-params\n\n` + `If you're unsure how to load polyfills, we recommend you check out ` + `https://polyfill.io/v3/ which provides some recommendations about how ` + `to load polyfills only for users that need them, instead of for every ` + `user.`) ;
  541. let defaultSearchParamsRef = React.useRef(createSearchParams(defaultInit));
  542. let hasSetSearchParamsRef = React.useRef(false);
  543. let location = useLocation();
  544. let searchParams = React.useMemo(() => // Only merge in the defaults if we haven't yet called setSearchParams.
  545. // Once we call that we want those to take precedence, otherwise you can't
  546. // remove a param with setSearchParams({}) if it has an initial value
  547. getSearchParamsForLocation(location.search, hasSetSearchParamsRef.current ? null : defaultSearchParamsRef.current), [location.search]);
  548. let navigate = useNavigate();
  549. let setSearchParams = React.useCallback((nextInit, navigateOptions) => {
  550. const newSearchParams = createSearchParams(typeof nextInit === "function" ? nextInit(searchParams) : nextInit);
  551. hasSetSearchParamsRef.current = true;
  552. navigate("?" + newSearchParams, navigateOptions);
  553. }, [navigate, searchParams]);
  554. return [searchParams, setSearchParams];
  555. }
  556. /**
  557. * Returns a function that may be used to programmatically submit a form (or
  558. * some arbitrary data) to the server.
  559. */
  560. function useSubmit() {
  561. return useSubmitImpl();
  562. }
  563. function useSubmitImpl(fetcherKey, routeId) {
  564. let {
  565. router
  566. } = useDataRouterContext(DataRouterHook.UseSubmitImpl);
  567. let defaultAction = useFormAction();
  568. return React.useCallback((target, options = {}) => {
  569. if (typeof document === "undefined") {
  570. throw new Error("You are calling submit during the server render. " + "Try calling submit within a `useEffect` or callback instead.");
  571. }
  572. let {
  573. method,
  574. encType,
  575. formData,
  576. url
  577. } = getFormSubmissionInfo(target, defaultAction, options);
  578. let href = url.pathname + url.search;
  579. let opts = {
  580. replace: options.replace,
  581. preventScrollReset: options.preventScrollReset,
  582. formData,
  583. formMethod: method,
  584. formEncType: encType
  585. };
  586. if (fetcherKey) {
  587. !(routeId != null) ? invariant(false, "No routeId available for useFetcher()") : void 0;
  588. router.fetch(fetcherKey, routeId, href, opts);
  589. } else {
  590. router.navigate(href, opts);
  591. }
  592. }, [defaultAction, router, fetcherKey, routeId]);
  593. }
  594. function useFormAction(action, {
  595. relative
  596. } = {}) {
  597. let {
  598. basename
  599. } = React.useContext(UNSAFE_NavigationContext);
  600. let routeContext = React.useContext(UNSAFE_RouteContext);
  601. !routeContext ? invariant(false, "useFormAction must be used inside a RouteContext") : void 0;
  602. let [match] = routeContext.matches.slice(-1); // Shallow clone path so we can modify it below, otherwise we modify the
  603. // object referenced by useMemo inside useResolvedPath
  604. let path = { ...useResolvedPath(action ? action : ".", {
  605. relative
  606. })
  607. }; // Previously we set the default action to ".". The problem with this is that
  608. // `useResolvedPath(".")` excludes search params and the hash of the resolved
  609. // URL. This is the intended behavior of when "." is specifically provided as
  610. // the form action, but inconsistent w/ browsers when the action is omitted.
  611. // https://github.com/remix-run/remix/issues/927
  612. let location = useLocation();
  613. if (action == null) {
  614. // Safe to write to these directly here since if action was undefined, we
  615. // would have called useResolvedPath(".") which will never include a search
  616. // or hash
  617. path.search = location.search;
  618. path.hash = location.hash; // When grabbing search params from the URL, remove the automatically
  619. // inserted ?index param so we match the useResolvedPath search behavior
  620. // which would not include ?index
  621. if (match.route.index) {
  622. let params = new URLSearchParams(path.search);
  623. params.delete("index");
  624. path.search = params.toString() ? `?${params.toString()}` : "";
  625. }
  626. }
  627. if ((!action || action === ".") && match.route.index) {
  628. path.search = path.search ? path.search.replace(/^\?/, "?index&") : "?index";
  629. } // If we're operating within a basename, prepend it to the pathname prior
  630. // to creating the form action. If this is a root navigation, then just use
  631. // the raw basename which allows the basename to have full control over the
  632. // presence of a trailing slash on root actions
  633. if (basename !== "/") {
  634. path.pathname = path.pathname === "/" ? basename : joinPaths([basename, path.pathname]);
  635. }
  636. return createPath(path);
  637. }
  638. function createFetcherForm(fetcherKey, routeId) {
  639. let FetcherForm = /*#__PURE__*/React.forwardRef((props, ref) => {
  640. return /*#__PURE__*/React.createElement(FormImpl, Object.assign({}, props, {
  641. ref: ref,
  642. fetcherKey: fetcherKey,
  643. routeId: routeId
  644. }));
  645. });
  646. {
  647. FetcherForm.displayName = "fetcher.Form";
  648. }
  649. return FetcherForm;
  650. }
  651. let fetcherId = 0;
  652. /**
  653. * Interacts with route loaders and actions without causing a navigation. Great
  654. * for any interaction that stays on the same page.
  655. */
  656. function useFetcher() {
  657. let {
  658. router
  659. } = useDataRouterContext(DataRouterHook.UseFetcher);
  660. let route = React.useContext(UNSAFE_RouteContext);
  661. !route ? invariant(false, `useFetcher must be used inside a RouteContext`) : void 0;
  662. let routeId = route.matches[route.matches.length - 1]?.route.id;
  663. !(routeId != null) ? invariant(false, `useFetcher can only be used on routes that contain a unique "id"`) : void 0;
  664. let [fetcherKey] = React.useState(() => String(++fetcherId));
  665. let [Form] = React.useState(() => {
  666. !routeId ? invariant(false, `No routeId available for fetcher.Form()`) : void 0;
  667. return createFetcherForm(fetcherKey, routeId);
  668. });
  669. let [load] = React.useState(() => href => {
  670. !router ? invariant(false, "No router available for fetcher.load()") : void 0;
  671. !routeId ? invariant(false, "No routeId available for fetcher.load()") : void 0;
  672. router.fetch(fetcherKey, routeId, href);
  673. });
  674. let submit = useSubmitImpl(fetcherKey, routeId);
  675. let fetcher = router.getFetcher(fetcherKey);
  676. let fetcherWithComponents = React.useMemo(() => ({
  677. Form,
  678. submit,
  679. load,
  680. ...fetcher
  681. }), [fetcher, Form, submit, load]);
  682. React.useEffect(() => {
  683. // Is this busted when the React team gets real weird and calls effects
  684. // twice on mount? We really just need to garbage collect here when this
  685. // fetcher is no longer around.
  686. return () => {
  687. if (!router) {
  688. console.warn(`No fetcher available to clean up from useFetcher()`);
  689. return;
  690. }
  691. router.deleteFetcher(fetcherKey);
  692. };
  693. }, [router, fetcherKey]);
  694. return fetcherWithComponents;
  695. }
  696. /**
  697. * Provides all fetchers currently on the page. Useful for layouts and parent
  698. * routes that need to provide pending/optimistic UI regarding the fetch.
  699. */
  700. function useFetchers() {
  701. let state = useDataRouterState(DataRouterStateHook.UseFetchers);
  702. return [...state.fetchers.values()];
  703. }
  704. const SCROLL_RESTORATION_STORAGE_KEY = "react-router-scroll-positions";
  705. let savedScrollPositions = {};
  706. /**
  707. * When rendered inside a RouterProvider, will restore scroll positions on navigations
  708. */
  709. function useScrollRestoration({
  710. getKey,
  711. storageKey
  712. } = {}) {
  713. let {
  714. router
  715. } = useDataRouterContext(DataRouterHook.UseScrollRestoration);
  716. let {
  717. restoreScrollPosition,
  718. preventScrollReset
  719. } = useDataRouterState(DataRouterStateHook.UseScrollRestoration);
  720. let location = useLocation();
  721. let matches = useMatches();
  722. let navigation = useNavigation(); // Trigger manual scroll restoration while we're active
  723. React.useEffect(() => {
  724. window.history.scrollRestoration = "manual";
  725. return () => {
  726. window.history.scrollRestoration = "auto";
  727. };
  728. }, []); // Save positions on pagehide
  729. usePageHide(React.useCallback(() => {
  730. if (navigation.state === "idle") {
  731. let key = (getKey ? getKey(location, matches) : null) || location.key;
  732. savedScrollPositions[key] = window.scrollY;
  733. }
  734. sessionStorage.setItem(storageKey || SCROLL_RESTORATION_STORAGE_KEY, JSON.stringify(savedScrollPositions));
  735. window.history.scrollRestoration = "auto";
  736. }, [storageKey, getKey, navigation.state, location, matches])); // Read in any saved scroll locations
  737. if (typeof document !== "undefined") {
  738. // eslint-disable-next-line react-hooks/rules-of-hooks
  739. React.useLayoutEffect(() => {
  740. try {
  741. let sessionPositions = sessionStorage.getItem(storageKey || SCROLL_RESTORATION_STORAGE_KEY);
  742. if (sessionPositions) {
  743. savedScrollPositions = JSON.parse(sessionPositions);
  744. }
  745. } catch (e) {// no-op, use default empty object
  746. }
  747. }, [storageKey]); // Enable scroll restoration in the router
  748. // eslint-disable-next-line react-hooks/rules-of-hooks
  749. React.useLayoutEffect(() => {
  750. let disableScrollRestoration = router?.enableScrollRestoration(savedScrollPositions, () => window.scrollY, getKey);
  751. return () => disableScrollRestoration && disableScrollRestoration();
  752. }, [router, getKey]); // Restore scrolling when state.restoreScrollPosition changes
  753. // eslint-disable-next-line react-hooks/rules-of-hooks
  754. React.useLayoutEffect(() => {
  755. // Explicit false means don't do anything (used for submissions)
  756. if (restoreScrollPosition === false) {
  757. return;
  758. } // been here before, scroll to it
  759. if (typeof restoreScrollPosition === "number") {
  760. window.scrollTo(0, restoreScrollPosition);
  761. return;
  762. } // try to scroll to the hash
  763. if (location.hash) {
  764. let el = document.getElementById(location.hash.slice(1));
  765. if (el) {
  766. el.scrollIntoView();
  767. return;
  768. }
  769. } // Don't reset if this navigation opted out
  770. if (preventScrollReset === true) {
  771. return;
  772. } // otherwise go to the top on new locations
  773. window.scrollTo(0, 0);
  774. }, [location, restoreScrollPosition, preventScrollReset]);
  775. }
  776. }
  777. /**
  778. * Setup a callback to be fired on the window's `beforeunload` event. This is
  779. * useful for saving some data to `window.localStorage` just before the page
  780. * refreshes.
  781. *
  782. * Note: The `callback` argument should be a function created with
  783. * `React.useCallback()`.
  784. */
  785. function useBeforeUnload(callback, options) {
  786. let {
  787. capture
  788. } = options || {};
  789. React.useEffect(() => {
  790. let opts = capture != null ? {
  791. capture
  792. } : undefined;
  793. window.addEventListener("beforeunload", callback, opts);
  794. return () => {
  795. window.removeEventListener("beforeunload", callback, opts);
  796. };
  797. }, [callback, capture]);
  798. }
  799. /**
  800. * Setup a callback to be fired on the window's `pagehide` event. This is
  801. * useful for saving some data to `window.localStorage` just before the page
  802. * refreshes. This event is better supported than beforeunload across browsers.
  803. *
  804. * Note: The `callback` argument should be a function created with
  805. * `React.useCallback()`.
  806. */
  807. function usePageHide(callback, options) {
  808. let {
  809. capture
  810. } = options || {};
  811. React.useEffect(() => {
  812. let opts = capture != null ? {
  813. capture
  814. } : undefined;
  815. window.addEventListener("pagehide", callback, opts);
  816. return () => {
  817. window.removeEventListener("pagehide", callback, opts);
  818. };
  819. }, [callback, capture]);
  820. }
  821. /**
  822. * Wrapper around useBlocker to show a window.confirm prompt to users instead
  823. * of building a custom UI with useBlocker.
  824. *
  825. * Warning: This has *a lot of rough edges* and behaves very differently (and
  826. * very incorrectly in some cases) across browsers if user click addition
  827. * back/forward navigations while the confirm is open. Use at your own risk.
  828. */
  829. function usePrompt({
  830. when,
  831. message
  832. }) {
  833. let blocker = unstable_useBlocker(when);
  834. React.useEffect(() => {
  835. if (blocker.state === "blocked" && !when) {
  836. blocker.reset();
  837. }
  838. }, [blocker, when]);
  839. React.useEffect(() => {
  840. if (blocker.state === "blocked") {
  841. let proceed = window.confirm(message);
  842. if (proceed) {
  843. setTimeout(blocker.proceed, 0);
  844. } else {
  845. blocker.reset();
  846. }
  847. }
  848. }, [blocker, message]);
  849. }
  850. ////////////////////////////////////////////////////////////////////////////////
  851. //#region Utils
  852. ////////////////////////////////////////////////////////////////////////////////
  853. function warning(cond, message) {
  854. if (!cond) {
  855. // eslint-disable-next-line no-console
  856. if (typeof console !== "undefined") console.warn(message);
  857. try {
  858. // Welcome to debugging React Router!
  859. //
  860. // This error is thrown as a convenience so you can more easily
  861. // find the source for a warning that appears in the console by
  862. // enabling "pause on exceptions" in your JavaScript debugger.
  863. throw new Error(message); // eslint-disable-next-line no-empty
  864. } catch (e) {}
  865. }
  866. } //#endregion
  867. export { BrowserRouter, Form, HashRouter, Link, NavLink, ScrollRestoration, useScrollRestoration as UNSAFE_useScrollRestoration, createBrowserRouter, createHashRouter, createSearchParams, HistoryRouter as unstable_HistoryRouter, usePrompt as unstable_usePrompt, useBeforeUnload, useFetcher, useFetchers, useFormAction, useLinkClickHandler, useSearchParams, useSubmit };
  868. //# sourceMappingURL=react-router-dom.development.js.map