UNPKG

react-router-loading

Version:

Wrapper for react-router that allows you to load data before switching the screen

291 lines (290 loc) 9.29 kB
var __defProp = Object.defineProperty; var __defProps = Object.defineProperties; var __getOwnPropDescs = Object.getOwnPropertyDescriptors; var __getOwnPropSymbols = Object.getOwnPropertySymbols; var __hasOwnProp = Object.prototype.hasOwnProperty; var __propIsEnum = Object.prototype.propertyIsEnumerable; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __spreadValues = (a, b) => { for (var prop in b || (b = {})) if (__hasOwnProp.call(b, prop)) __defNormalProp(a, prop, b[prop]); if (__getOwnPropSymbols) for (var prop of __getOwnPropSymbols(b)) { if (__propIsEnum.call(b, prop)) __defNormalProp(a, prop, b[prop]); } return a; }; var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b)); var __objRest = (source, exclude) => { var target = {}; for (var prop in source) if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0) target[prop] = source[prop]; if (source != null && __getOwnPropSymbols) for (var prop of __getOwnPropSymbols(source)) { if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop)) target[prop] = source[prop]; } return target; }; import React, { createContext, useMemo, useContext, useState, useRef, useEffect, useCallback } from "react"; import _topbar from "topbar"; import { Route as Route$1, matchRoutes, useRoutes, UNSAFE_LocationContext, useLocation, useNavigationType } from "react-router"; const LoadingContext = createContext({ start: () => { }, done: () => { }, restart: () => { } }); LoadingContext.displayName = "LoadingContext"; const LoadingGetterContext = createContext(false); LoadingGetterContext.displayName = "LoadingGetterContext"; var jsxRuntime = { exports: {} }; var reactJsxRuntime_production_min = {}; /** * @license React * react-jsx-runtime.production.min.js * * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ var f = React, k = Symbol.for("react.element"), l = Symbol.for("react.fragment"), m = Object.prototype.hasOwnProperty, n = f.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner, p = { key: true, ref: true, __self: true, __source: true }; function q(c, a, g) { var b, d = {}, e = null, h = null; g !== void 0 && (e = "" + g); a.key !== void 0 && (e = "" + a.key); a.ref !== void 0 && (h = a.ref); for (b in a) m.call(a, b) && !p.hasOwnProperty(b) && (d[b] = a[b]); if (c && c.defaultProps) for (b in a = c.defaultProps, a) d[b] === void 0 && (d[b] = a[b]); return { $$typeof: k, type: c, key: e, ref: h, props: d, _owner: n.current }; } reactJsxRuntime_production_min.Fragment = l; reactJsxRuntime_production_min.jsx = q; reactJsxRuntime_production_min.jsxs = q; { jsxRuntime.exports = reactJsxRuntime_production_min; } const jsx = jsxRuntime.exports.jsx; const jsxs = jsxRuntime.exports.jsxs; const Fragment = jsxRuntime.exports.Fragment; const Route = (_a) => { var _b = _a, { loading } = _b, props = __objRest(_b, [ "loading" ]); return /* @__PURE__ */ jsx(Route$1, __spreadValues({}, props)); }; const DefaultLoadingScreen = () => /* @__PURE__ */ jsx("div", { style: { position: "fixed", top: 0, bottom: 0, left: 0, right: 0, backgroundColor: "white" } }); function createRoutesFromChildren(children) { const routes = []; React.Children.forEach(children, (element) => { if (!React.isValidElement(element)) { return; } if (element.type === React.Fragment) { routes.push.apply(routes, createRoutesFromChildren(element.props.children)); return; } const route = { caseSensitive: element.props.caseSensitive, element: element.props.element, index: element.props.index, path: element.props.path, loading: element.props.loading }; if (element.props.children) { route.children = createRoutesFromChildren(element.props.children); } routes.push(route); }); return routes; } const isPathsDifferent = (first, second) => first.pathname !== second.pathname; const isPathsEqual = (first, second) => first.pathname === second.pathname; const isSearchDifferent = (first, second) => first.search !== second.search; const isLoadable = (location, routes) => { const matches = matchRoutes(routes, location); if (!matches || matches.length === 0) return false; const lastMatch = matches[matches.length - 1]; return lastMatch.route.loading; }; const RouteWrapper = ({ routes, location, navigationType, hidden }) => { const element = useRoutes(routes, location); return /* @__PURE__ */ jsx("div", { style: hidden ? { display: "none" } : void 0, children: useMemo(() => /* @__PURE__ */ jsx(UNSAFE_LocationContext.Provider, { value: { location, navigationType }, children: element }), [location]) }); }; const LOADING_PATHNAME = "__loading"; const LoadingRoutes = ({ children, loadingScreen: LoadingScreen, maxLoadingTime = 0 }) => { const location = useLocation(); const navigationType = useNavigationType(); const loadingContext = useContext(LoadingContext); const isCurrentlyLoading = useContext(LoadingGetterContext); const routes = useMemo(() => createRoutesFromChildren(children), [children]); const [current, setCurrent] = useState(() => { const isFirstPageLoadable = isLoadable(location, routes); const firstLocation = isFirstPageLoadable ? __spreadProps(__spreadValues({}, location), { pathname: LOADING_PATHNAME }) : location; return { location: firstLocation, navigationType }; }); const [next, setNext] = useState(current); const timeout = useRef(); useEffect(() => { if (isPathsDifferent(location, next.location)) { const isPageLoadable = isLoadable(location, routes); setNext({ location: __spreadValues({}, location), navigationType }); if (!isPageLoadable) { loadingContext.done(); setCurrent({ location: __spreadValues({}, location), navigationType }); } else { if (!isCurrentlyLoading) loadingContext.start(); else loadingContext.restart(); } } if (isPathsEqual(location, current.location)) { loadingContext.done(); if (isSearchDifferent(location, current.location)) setCurrent({ location: __spreadValues({}, location), navigationType }); } }, [location]); useEffect(() => { if (!isCurrentlyLoading && isPathsDifferent(current.location, next.location)) setCurrent(next); }, [isCurrentlyLoading]); useEffect(() => { if (maxLoadingTime > 0) { if (timeout.current) { clearTimeout(timeout.current); timeout.current = void 0; } if (isPathsDifferent(current.location, next.location)) { timeout.current = setTimeout(() => { loadingContext.done(); }, maxLoadingTime); } } }, [current, next]); return useMemo(() => /* @__PURE__ */ jsxs(Fragment, { children: [current.location.pathname !== LOADING_PATHNAME ? /* @__PURE__ */ jsx(RouteWrapper, { routes, location: current.location, navigationType: current.navigationType }, current.location.pathname) : LoadingScreen ? /* @__PURE__ */ jsx(LoadingScreen, {}) : /* @__PURE__ */ jsx(DefaultLoadingScreen, {}), isPathsDifferent(current.location, next.location) && /* @__PURE__ */ jsx(RouteWrapper, { routes, location: next.location, navigationType: next.navigationType, hidden: true }, next.location.pathname)] }), [current, next]); }; const LoadingMiddleware = ({ children, isLoading = false }) => { const [loading, setLoading] = useState(isLoading); const isFirstRender = useRef(true); const start = useCallback(() => { topbar.show(); setLoading(true); }, []); const done = useCallback(() => { topbar.hide(); setLoading(false); }, []); const restart = useCallback(() => { topbar.hide(); topbar.show(); }, []); useEffect(() => { if (!isFirstRender.current) { if (isLoading && !loading) start(); else if (loading) done(); } else { isFirstRender.current = false; } }, [isLoading]); const loadingProvider = useMemo(() => /* @__PURE__ */ jsx(LoadingContext.Provider, { value: { start, done, restart }, children }), []); return /* @__PURE__ */ jsx(LoadingGetterContext.Provider, { value: loading, children: loadingProvider }); }; const Routes = ({ children, loadingScreen, maxLoadingTime, isLoading }) => /* @__PURE__ */ jsx(LoadingMiddleware, { isLoading, children: /* @__PURE__ */ jsx(LoadingRoutes, { loadingScreen, maxLoadingTime, children }) }); const topbar = _topbar; const useLoadingContext = () => useContext(LoadingContext); export { LoadingContext, LoadingGetterContext, Route, Routes, topbar, useLoadingContext }; //# sourceMappingURL=react-router-loading.es.js.map