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
JavaScript
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