UNPKG

@suspensive/react

Version:

Suspensive interfaces for react

1 lines 9.19 kB
{"version":3,"file":"lazy.mjs","names":["originalLazy"],"sources":["../src/lazy.ts"],"sourcesContent":["'use client'\nimport { type ComponentType, type LazyExoticComponent, lazy as originalLazy } from 'react'\nimport { noop } from './utils/noop'\n\ninterface LazyOptions {\n onSuccess?: ({ load }: { load: () => Promise<void> }) => void\n onError?: ({ error, load }: { error: unknown; load: () => Promise<void> }) => undefined\n}\n\n/**\n * Creates a lazy function with custom default options\n *\n * The default `lazy` export is equivalent to `createLazy({})`.\n *\n * @experimental This is experimental feature.\n *\n * @description\n * The created lazy function will execute individual callbacks first, then default callbacks.\n * For onSuccess: individual onSuccess → default onSuccess\n * For onError: individual onError → default onError\n *\n * @example\n * ```tsx\n * import { createLazy } from '@suspensive/react'\n *\n * // Create a lazy factory with custom default options\n * const lazy = createLazy({\n * onSuccess: () => console.log('Component loaded successfully'),\n * onError: ({ error }) => console.error('Component loading failed:', error)\n * })\n *\n * // Use the factory to create lazy components\n * const Component1 = lazy(() => import('./Component1'))\n * const Component2 = lazy(() => import('./Component2'))\n *\n * // Individual options are executed after default options\n * const Component3 = lazy(() => import('./Component3'), {\n * onError: ({ error }) => {\n * console.error('Individual error handling:', error)\n * // This callback runs after the default onError callback\n * }\n * })\n * ```\n */\nexport const createLazy =\n (defaultOptions: LazyOptions) =>\n <T extends ComponentType<any>>(\n load: () => Promise<{ default: T }>,\n options?: LazyOptions\n ): LazyExoticComponent<T> & {\n load: () => Promise<void>\n } => {\n const composedOnSuccess = ({ load }: { load: () => Promise<void> }) => {\n options?.onSuccess?.({ load })\n defaultOptions.onSuccess?.({ load })\n }\n\n const composedOnError = ({ error, load }: { error: unknown; load: () => Promise<void> }) => {\n options?.onError?.({ error, load })\n defaultOptions.onError?.({ error, load })\n }\n\n const loadNoReturn = () => load().then(noop)\n return Object.assign(\n originalLazy(() =>\n load().then(\n (loaded) => {\n composedOnSuccess({ load: loadNoReturn })\n return loaded\n },\n (error: unknown) => {\n composedOnError({ error: error, load: loadNoReturn })\n throw error\n }\n )\n ),\n { load: loadNoReturn }\n )\n }\n\n/**\n * A wrapper around React.lazy that provides error handling and success callbacks\n *\n * This is equivalent to `createLazy({})` - a lazy function with no default options.\n *\n * @experimental This is experimental feature.\n *\n * @example\n * ```tsx\n * import { lazy, Suspense } from '@suspensive/react'\n *\n * // Basic usage\n * const Component = lazy(() => import('./Component'))\n *\n * // With error handling and success callbacks\n * const ReloadComponent = lazy(() => import('./ReloadComponent'), {\n * onError: ({ error }) => console.error('Loading failed:', error),\n * onSuccess: () => console.log('Component loaded successfully')\n * })\n *\n * // Preloading component\n * function PreloadExample() {\n * const handlePreload = () => {\n * Component.load() // Preload the component\n * }\n *\n * return (\n * <div>\n * <button onClick={handlePreload}>Preload Component</button>\n * <Suspense fallback={<div>Loading...</div>}>\n * <Component />\n * </Suspense>\n * </div>\n * )\n * }\n *\n * // Using createLazy for factory pattern\n * const lazy = createLazy({\n * onError: ({ error }) => console.error('Default error handling:', error)\n * })\n * const CustomComponent = lazy(() => import('./CustomComponent'))\n * ```\n *\n * @returns A lazy component with additional `load` method for preloading\n * @property {() => Promise<void>} load - Preloads the component without rendering it. Useful for prefetching components in the background.\n */\nexport const lazy = createLazy({})\n\ninterface ReloadOnErrorStorage {\n getItem: (key: string) => string | null\n setItem: (key: string, value: string) => void\n removeItem: (key: string) => void\n}\n\ninterface ReloadOnErrorOptions extends LazyOptions {\n /**\n * The number of times to retry the loading of the component. \\\n * If `true`, the component will be retried indefinitely.\n *\n * @default 1\n */\n retry?: number | boolean\n /**\n * The delay between retries. \\\n * If a function is provided, it will be called with the current retry count.\n *\n * @default 0\n */\n retryDelay?: number | ((retryCount: number) => number)\n /**\n * The storage to use for the retry count. \\\n * If not provided, it assumes that you are in a browser environment and uses `window.sessionStorage`.\n *\n * @default window.sessionStorage\n */\n storage?: ReloadOnErrorStorage\n /**\n * The function to use to reload the component. \\\n * If not provided, it assumes that you are in a browser environment and uses `window.location.reload`.\n *\n * @default window.location.reload\n */\n reload?: (timeoutId: ReturnType<typeof setTimeout>) => void\n}\n\n/**\n * Options for reloading page if the component fails to load.\n *\n * @experimental This is experimental feature.\n *\n * @example\n * ```tsx\n * import { createLazy, reloadOnError } from '@suspensive/react'\n *\n * const lazy = createLazy(reloadOnError({ retry: 1, retryDelay: 1000 }))\n * const Component = lazy(() => import('./Component'))\n * ```\n */\nexport const reloadOnError = ({\n retry = 1,\n retryDelay = 0,\n storage,\n reload,\n ...options\n}: ReloadOnErrorOptions): LazyOptions => {\n const reloadStorage = (() => {\n if (storage) return storage\n if (typeof window !== 'undefined' && 'sessionStorage' in window) return window.sessionStorage\n throw new Error('[@suspensive/react] No storage provided and no sessionStorage in window')\n })()\n const reloadFunction = (() => {\n if (reload) return reload\n if (typeof window !== 'undefined' && 'location' in window) return () => window.location.reload()\n throw new Error('[@suspensive/react] No reload function provided and no location in window')\n })()\n\n return {\n ...options,\n onSuccess: ({ load }) => {\n options.onSuccess?.({ load })\n reloadStorage.removeItem(load.toString())\n },\n onError: ({ error, load }) => {\n options.onError?.({ error, load })\n\n const storageKey = load.toString()\n let currentRetryCount = 0\n\n if (typeof retry === 'number') {\n const storedValue = reloadStorage.getItem(storageKey)\n if (storedValue) {\n const reloadCount = parseInt(storedValue, 10)\n if (Number.isNaN(reloadCount)) reloadStorage.removeItem(storageKey)\n currentRetryCount = reloadCount\n }\n }\n\n const shouldRetry = retry === true || (typeof retry === 'number' && currentRetryCount < retry)\n if (!shouldRetry) return\n if (typeof retry === 'number') reloadStorage.setItem(storageKey, String(currentRetryCount + 1))\n const delayValue = typeof retryDelay === 'function' ? retryDelay(currentRetryCount) : retryDelay\n const timeoutId = setTimeout(() => {\n reloadFunction(timeoutId)\n }, delayValue)\n },\n }\n}\n"],"mappings":";;;;;;;;;CAmLE;CACA;CACA;CACA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA1IF,MAAa,cACV,oBAEC,MACA,YAGG;CACH,MAAM,qBAAqB,EAAE,WAA0C;;EACrE,wEAAS,yFAAY,EAAE,KAAK,CAAC;EAC7B,wCAAe,sGAAY,EAAE,KAAK,CAAC;CACrC;CAEA,MAAM,mBAAmB,EAAE,OAAO,WAA0D;;EAC1F,sEAAS,mFAAU;GAAE;GAAO;EAAK,CAAC;EAClC,wCAAe,oGAAU;GAAE;GAAO;EAAK,CAAC;CAC1C;CAEA,MAAM,qBAAqB,KAAK,EAAE,KAAK,IAAI;CAC3C,OAAO,OAAO,OACZA,aACE,KAAK,EAAE,MACJ,WAAW;EACV,kBAAkB,EAAE,MAAM,aAAa,CAAC;EACxC,OAAO;CACT,IACC,UAAmB;EAClB,gBAAgB;GAAS;GAAO,MAAM;EAAa,CAAC;EACpD,MAAM;CACR,CACF,CACF,GACA,EAAE,MAAM,aAAa,CACvB;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgDF,MAAa,OAAO,WAAW,CAAC,CAAC;;;;;;;;;;;;;;AAoDjC,MAAa,iBAAiB,SAMW;KANX,EAC5B,QAAQ,GACR,aAAa,GACb,SACA,iBACG;CAEH,MAAM,uBAAuB;EAC3B,IAAI,SAAS,OAAO;EACpB,IAAI,OAAO,WAAW,eAAe,oBAAoB,QAAQ,OAAO,OAAO;EAC/E,MAAM,IAAI,MAAM,yEAAyE;CAC3F,GAAG;CACH,MAAM,wBAAwB;EAC5B,IAAI,QAAQ,OAAO;EACnB,IAAI,OAAO,WAAW,eAAe,cAAc,QAAQ,aAAa,OAAO,SAAS,OAAO;EAC/F,MAAM,IAAI,MAAM,2EAA2E;CAC7F,GAAG;CAEH,yCACK;EACH,YAAY,EAAE,WAAW;;GACvB,+BAAQ,2FAAY,EAAE,KAAK,CAAC;GAC5B,cAAc,WAAW,KAAK,SAAS,CAAC;EAC1C;EACA,UAAU,EAAE,OAAO,WAAW;;GAC5B,6BAAQ,qFAAU;IAAE;IAAO;GAAK,CAAC;GAEjC,MAAM,aAAa,KAAK,SAAS;GACjC,IAAI,oBAAoB;GAExB,IAAI,OAAO,UAAU,UAAU;IAC7B,MAAM,cAAc,cAAc,QAAQ,UAAU;IACpD,IAAI,aAAa;KACf,MAAM,cAAc,SAAS,aAAa,EAAE;KAC5C,IAAI,OAAO,MAAM,WAAW,GAAG,cAAc,WAAW,UAAU;KAClE,oBAAoB;IACtB;GACF;GAGA,IAAI,EADgB,UAAU,QAAS,OAAO,UAAU,YAAY,oBAAoB,QACtE;GAClB,IAAI,OAAO,UAAU,UAAU,cAAc,QAAQ,YAAY,OAAO,oBAAoB,CAAC,CAAC;GAC9F,MAAM,aAAa,OAAO,eAAe,aAAa,WAAW,iBAAiB,IAAI;GACtF,MAAM,YAAY,iBAAiB;IACjC,eAAe,SAAS;GAC1B,GAAG,UAAU;EACf;EACF;AACF"}