UNPKG

react-history-switch

Version:

Self-hosted context-free Switch routing component for History.js library (React). The library was created to transfer navigation responsibility from a view into Mobx state container (MVC). Also can be used separately as a self-hosted router

321 lines (272 loc) 7.84 kB
import * as React from 'react'; import React__default, { createContext, useContext, useState, useMemo, useEffect, useRef } from 'react'; import { createMemoryHistory, createBrowserHistory } from 'history'; import { pathToRegexp } from 'path-to-regexp'; function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; } const Context = createContext(createMemoryHistory()); const HistoryContext = ({ children, history }) => React.createElement(Context.Provider, { value: history }, children); const useHistoryContext = () => useContext(Context); const _excluded$1 = ["to"]; const Link = _ref => { let { to } = _ref, otherProps = _objectWithoutPropertiesLoose(_ref, _excluded$1); const history = useHistoryContext(); return React.createElement("a", Object.assign({}, otherProps, { href: '#', onClick: () => history.push(to) })); }; const Error = () => React__default.createElement("p", null, "500"); const queued = promise => { let lastPromise = Promise.resolve(); const wrappedFn = (...args) => { lastPromise = lastPromise.then(() => promise(...args)).finally(() => wrappedFn.clear()); return lastPromise; }; wrappedFn.clear = () => { lastPromise = Promise.resolve(); }; return wrappedFn; }; const Async = ({ children, Loader: _Loader = () => null, Error: _Error = () => null, onLoadStart, onLoadEnd, payload, deps: _deps = [] }) => { const [child, setChild] = useState(''); const [loading, setLoading] = useState(false); const [error, setError] = useState(false); const execute = useMemo(() => queued(async payload => { let isOk = true; setLoading(true); setError(false); onLoadStart && onLoadStart(); try { const result = children(payload); if (result instanceof Promise) { return (await result) || null; } else { return result || null; } } catch (e) { isOk = false; } finally { setLoading(false); setError(!isOk); onLoadEnd && onLoadEnd(isOk); } return null; }), []); useEffect(() => { const process = async () => { const result = await execute(payload); setChild(result); }; process(); }, [payload, ..._deps]); if (loading) { return React.createElement(_Loader, null); } else if (error) { return React.createElement(_Error, null); } else { return React.createElement(React.Fragment, null, child); } }; const Loader = () => React__default.createElement("p", null, "Loading"); const _excluded = ["Loader", "Error", "onLoadEnd", "onLoadStart", "children", "state", "payload"]; const FetchView = _ref => { let { Loader: Loader$1 = Loader, Error: Error$1 = Error, onLoadEnd, onLoadStart, children, state, payload } = _ref, otherProps = _objectWithoutPropertiesLoose(_ref, _excluded); const handleData = async payload => { if (Array.isArray(state)) { return await Promise.all(state.map(item => item(payload))); } else { return [await state(payload)]; } }; return React__default.createElement(Async, Object.assign({}, otherProps, { Loader: Loader$1, Error: Error$1, onLoadStart: onLoadStart, onLoadEnd: onLoadEnd, payload: payload }), async payload => { const data = await handleData(payload); return children(...data); }); }; const Forbidden = () => React__default.createElement("p", null, "Forbidden"); const NotFound = () => React__default.createElement("p", null, "Not found"); const createWindowHistory = () => { if (window.location.protocol === 'file:') { return createMemoryHistory(); } else { return createBrowserHistory(); } }; const sleep = (timeout = 1000) => new Promise(resolve => setTimeout(() => resolve(), timeout)); const canActivate = async item => { const { guard = () => true } = item; const isAvailable = guard(); if (isAvailable instanceof Promise) { return await isAvailable; } else { return isAvailable; } }; const DEFAULT_HISTORY = createWindowHistory(); const Fragment = () => React__default.createElement(React__default.Fragment, null); const Switch = ({ Loader: _Loader = Loader, Forbidden: _Forbidden = Forbidden, NotFound: _NotFound = NotFound, Error: _Error = Error, history: _history = DEFAULT_HISTORY, items, onLoadStart, onLoadEnd }) => { const unloadRef = useRef(null); const [location, setLocation] = useState(_extends({}, _history.location)); useEffect(() => { const handleLocation = update => { if (update.location.pathname !== location.pathname) { const newLocation = _extends({}, update.location); setLocation(newLocation); } }; return _history.listen(handleLocation); }, [_history, location]); const handleState = useMemo(() => async url => { unloadRef.current && (await unloadRef.current()); for (const item of items) { const { element = Fragment, redirect, prefetch, unload, path } = item; const params = {}; const keys = []; const reg = pathToRegexp(path, keys); const match = reg.test(url); const buildParams = () => { const tokens = reg.exec(url); tokens && keys.forEach((key, i) => { params[key.name] = tokens[i + 1]; }); }; const provideUnloadRef = () => { if (unload) { unloadRef.current = async () => { await Promise.resolve(unload(params)); unloadRef.current = null; }; } }; if (match) { if (await canActivate(item)) { buildParams(); prefetch && Object.assign(params, await prefetch(params)); provideUnloadRef(); if (typeof redirect === 'string') { setLocation(location => _extends({}, location, { pathname: redirect })); return { element: Fragment }; } if (typeof redirect === 'function') { const result = redirect(params); if (result !== null) { setLocation(location => _extends({}, location, { pathname: result })); return { element: Fragment }; } } return { element, params }; } return { element: _Forbidden }; } } return { element: _NotFound }; }, [location]); return React__default.createElement(HistoryContext, { history: _history }, React__default.createElement(FetchView, { state: handleState, Loader: _Loader, Error: _Error, payload: location.pathname, onLoadStart: onLoadStart, onLoadEnd: onLoadEnd }, async data => { const { element: Element = Fragment, params } = data; /* delay to prevent sync execution for appear animation */ await sleep(0); return React__default.createElement(Element, Object.assign({}, params)); })); }; export { Link, Switch }; //# sourceMappingURL=index.modern.js.map