@25sprout/react-starter
Version:
25sprout web starter with React
118 lines (93 loc) • 2.95 kB
text/typescript
import {
useState,
useEffect,
useMemo,
lazy,
useRef,
FC,
ReactElement,
ComponentType,
useCallback,
} from 'react';
import UniversalRouter, { RouterOptions, RouteContext, Route } from 'universal-router';
import { routeChange } from 'models/routing';
import { History } from 'history';
import { Store } from 'redux';
import { State } from 'models/reducers';
import useLocation from './useLocation';
export interface CustomRouteContext extends RouteContext {
history: History;
store: Store<State>;
}
export interface CustomRoute extends Route {
onEnter?: (R: CustomRouteContext) => Promise<ReactElement | boolean | void | undefined>;
components: () => Promise<{ default: ComponentType }>[];
render: (C: FC[], R: ReactElement) => ReactElement;
children?: CustomRoute[];
}
const options: RouterOptions = {
baseUrl: '',
async resolveRoute(ctx) {
const route = ctx.route as CustomRoute;
const { next } = ctx;
let children;
if (typeof route.onEnter === 'function') {
children = await route.onEnter(ctx as CustomRouteContext);
}
// Do not Enter children
if (children === false) {
return null;
}
if (typeof children === 'undefined') {
children = await next();
}
// Skip routes without render() function
if (!route.render) {
return null;
}
// Start downloading missing JavaScript chunks
const components = route.components
? route.components().map(component => lazy(() => component))
: [];
const result = route.render(components, children);
return result;
},
errorHandler(error, context) {
console.info('errorHandler: ', error, context);
return error.status === 404 ? '<h1>Page Not Found</h1>' : '<h1>Oops! Something went wrong</h1>';
},
};
interface useRouterReturnType {
loading: boolean;
component: ReactElement | null;
}
const useRouter = (routes: CustomRoute, history: History, store: Store): useRouterReturnType => {
const location = useLocation(history);
const router = useMemo(() => new UniversalRouter(routes, options), [routes]);
const [Component, setComponent] = useState({ loading: false, component: null });
// Referrence the route index
const lastIndex = useRef(0);
const asyncLocationChange = useCallback(async () => {
setComponent(prevComponent => ({ ...prevComponent, loading: true }));
lastIndex.current += 1;
// Use function scope to index current change route
const index = lastIndex.current;
const LazyComponent = await router.resolve({
pathname: location.pathname,
history,
store,
});
// Detect the latest change index for prevent updating the wrong route view
if (index === lastIndex.current) {
setComponent({ loading: false, component: LazyComponent });
}
}, [history, location.pathname, router, store]);
useEffect(() => {
asyncLocationChange();
}, [asyncLocationChange]);
useEffect(() => {
store.dispatch(routeChange(location));
}, [location]);
return Component;
};
export default useRouter;