From e66910ec3807862851255111da829a3bb3a52235 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tiago=20Peres=20Fran=C3=A7a?= Date: Wed, 26 Jun 2024 16:30:25 -0300 Subject: [PATCH 1/2] async view hook --- packages/runtime/src/index.ts | 1 + packages/runtime/src/use-async-view.tsx | 78 +++++++++++++++++++++++++ 2 files changed, 79 insertions(+) create mode 100644 packages/runtime/src/use-async-view.tsx diff --git a/packages/runtime/src/index.ts b/packages/runtime/src/index.ts index 714fd5a..698bae4 100644 --- a/packages/runtime/src/index.ts +++ b/packages/runtime/src/index.ts @@ -3,4 +3,5 @@ export { Link } from './Link' export { AnyRoute, Route } from './Route' export * from './errors' export { AnyRouteWithParams } from './types' +export { useAsyncView } from './use-async-view' export { useNavigationList } from './use-navigation-list' diff --git a/packages/runtime/src/use-async-view.tsx b/packages/runtime/src/use-async-view.tsx new file mode 100644 index 0000000..3a39d51 --- /dev/null +++ b/packages/runtime/src/use-async-view.tsx @@ -0,0 +1,78 @@ +import { FunctionComponent, ReactNode, useCallback, useState } from 'react' + +interface AsyncViewConfig { + /** + * A component to render when the view can't be loaded (error). + * + * This component receives the prop "refresh" which is a function that refreshes the app when called. + */ + ErrorComponent?: FunctionComponent<{ refresh: () => void }>, + /** + * Whether or not to automatically refresh the view with cache disabled when an error occurs. + * + * If another error happens after refreshing, the `errorComponent` is rendered, it doesn't refresh again. + */ + shouldRefreshOnError?: boolean, + /** + * The initial value for `content`. + */ + initial?: ReactNode, +} + +// used to force the app to retrieve the latest version of index.html. +const refreshAppParam = 'update-app' + +function isAppRefreshed() { + return !!location.href.match(`[?&]${refreshAppParam}=\\d+`) +} + +function refreshApp() { + const now = new Date().getTime() + const newUrl = isAppRefreshed() + ? location.href.replace(new RegExp(`([?&]${refreshAppParam}=)\\d+`), `$1${now}`) + : location.href.replace(/(\?.*)?$/, `$1${location.href.includes('?') ? '&' : '?'}${refreshAppParam}=${now}`) + history.replaceState(null, '', newUrl) + location.reload() +} + +/** + * A hook for helping loading views asynchronously. + * + * Example: + * ```tsx + * const PageRenderer = () => { + * const { load, content } = useAsyncView({ ErrorComponent: UnderMaintenance }) + * useNavigationContext((context) => { + * context.when('root', props => load(() => import('./Home'), 'Home', props)) + * }) + * return content + * } + * ``` + * @param options the options for loading async views. + * @returns the values and functions for manipulating the current view (content). + */ +export function useAsyncView({ ErrorComponent, shouldRefreshOnError = true, initial }: AsyncViewConfig = {}) { + const [content, setContent] = useState(initial) + const load = useCallback(async< + Props extends object, + Import extends Record>, + Key extends keyof Import, + >(loader: () => Promise, key: Key, props: Props) => { + try { + const View: React.FunctionComponent = (await loader())[key] + setContent() + } catch (error) { + if (!shouldRefreshOnError || isAppRefreshed()) { + // eslint-disable-next-line no-console + console.error(error) + setContent(ErrorComponent ? :

Error while loading the view.

) + return + } + // eslint-disable-next-line no-console + console.warn('Error while loading page. This is probably because a new version of the site is available. Refreshing...') + refreshApp() + } + }, []) + + return { load, content, setContent } +} From 309edc3b42aa7c9929352c9bad12436bb33111ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tiago=20Peres=20Fran=C3=A7a?= Date: Wed, 26 Jun 2024 16:49:39 -0300 Subject: [PATCH 2/2] version up --- packages/runtime/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/runtime/package.json b/packages/runtime/package.json index 115730d..1c19bd5 100644 --- a/packages/runtime/package.json +++ b/packages/runtime/package.json @@ -1,6 +1,6 @@ { "name": "@stack-spot/citron-navigator", - "version": "1.3.1", + "version": "1.4.0", "type": "module", "main": "dist/index.js", "types": "dist/index.d.ts",