blade
Version:
React at the edge.
85 lines (83 loc) • 3.91 kB
JavaScript
import { _ as RootClientContext, c as usePopulatePathname, d as useUniversalContext, t as usePageTransition, u as usePrivateLocation } from "./hooks-BCUm8YC_.js";
import { t as wrapClientComponent } from "./wrap-client-DyCFsAJ7.js";
import { useEffect, useLayoutEffect, useRef, useState } from "react";
import { Fragment, jsx } from "react/jsx-runtime";
//#region private/client/components/history.tsx
const HistoryContent = ({ children }) => {
const universalContext = useUniversalContext();
const { transitionPage } = usePageTransition();
const { pathname, search, hash } = usePrivateLocation();
const populatedPathname = usePopulatePathname()(pathname);
const lastPopulatedPathname = useRef(null);
const lastHash = useRef(null);
useEffect(() => {
const pageChanged = () => {
const newLocation = window.location;
transitionPage(newLocation.pathname + newLocation.search + newLocation.hash, { updateAddressBar: false });
};
window.addEventListener("popstate", pageChanged);
return () => window.removeEventListener("popstate", pageChanged);
}, [transitionPage]);
useLayoutEffect(() => {
if (!lastPopulatedPathname.current) return;
if (!universalContext.addressBarInSync) return;
history.pushState({}, "", populatedPathname + search + hash);
if (hash && lastHash.current !== hash) {
document.querySelector(hash)?.scrollIntoView();
return;
}
if (populatedPathname && lastPopulatedPathname.current !== populatedPathname) {
document.documentElement.scrollTop = 0;
document.body.scrollTop = 0;
}
}, [populatedPathname + search + hash]);
useLayoutEffect(() => {
lastPopulatedPathname.current = populatedPathname;
lastHash.current = hash;
}, [populatedPathname + search + hash]);
return /* @__PURE__ */ jsx(Fragment, { children });
};
const History = ({ children, universalContext }) => {
/**
* Used for persisting the execution of a query across renders.
*
* Whenever a query is executed on the client, it is sent to the edge and executed
* there, after which the page is re-rendered with the new data. As a result, the
* original instance of the `useMutation` hook that ran the query might no longer be
* present (for example if the surrounding component was unmounted). In order for its
* associated promise to still resolve, we need to persist it across renders.
*/
const deferredPromises = useRef({});
/**
* Used for immediately updating the client-side query string parameters (in the
* address bar), while the server is still rendering the respective update for the page.
*
* This is useful in cases where client-side state is derived from the URL, such as when
* using a `?search` query parameter to filter a list of items. In those cases, the URL
* can be updated immediately to avoid having to store additional state on the client,
* and then the server will process the re-rendering of the page in the meantime.
*
* We're intentionally and explicitly only doing this for query string parameters and
* not for the pathname, because we would otherwise have to require an unpopulated
* pathname to be passed to `redirect()` calls, which currently accepts populated
* pathnames as well. Only the server can resolve the unpopulated version (which is
* essentially the file system path of the page) of a pathname, so we can't do this
* update on the client alone.
*/
const [clientQueryParams, setClientQueryParams] = useState(null);
useEffect(() => setClientQueryParams(null), [universalContext.url]);
const parsedURL = new URL(universalContext.url);
if (clientQueryParams) parsedURL.search = clientQueryParams;
return /* @__PURE__ */ jsx(RootClientContext.Provider, {
value: {
...universalContext,
url: parsedURL.href,
setClientQueryParams,
deferredPromises
},
children: /* @__PURE__ */ jsx(HistoryContent, { children })
});
};
wrapClientComponent(History, "History");
//#endregion
export { History as t };