UNPKG

react-imported-component

Version:
132 lines (131 loc) 4.21 kB
import { lazy, useCallback, useContext, useEffect, useMemo, useState } from 'react'; import { getLoadable } from '../loadable/loadable'; import { consumeMark } from '../loadable/marks'; import { isItReady } from '../loadable/pending'; import { isBackend } from '../utils/detectBackend'; import { es6import } from '../utils/utils'; import { streamContext } from './context'; function loadLoadable(loadable, callback) { const upd = () => callback({}); loadable.loadIfNeeded().then(upd, upd); } function updateLoadable(loadable, callback) { // support HMR if (process.env.NODE_ENV === 'development') { const upd = () => callback({}); loadable._probeChanges().then((changed) => changed && upd(), upd); } } /** * react hook to wrap `import` with a tracker * used by {@link useImported} * @internal */ export function useLoadable(loadable, options = {}) { const UID = useContext(streamContext); const wasDone = loadable.done; const [, forceUpdate] = useState({}); useMemo(() => { if (options.import !== false) { if (options.track !== false) { consumeMark(UID, loadable.mark); } if (!wasDone) { loadLoadable(loadable, forceUpdate); } else { updateLoadable(loadable, forceUpdate); } } return true; }, [loadable, options.import, options.track]); if (isBackend && !isItReady() && loadable.isLoading()) { /* tslint:disable:next-line no-console */ console.error('react-imported-component: trying to render a component which is not ready. You should `await whenComponentsReady()`?'); } // use mark // retry const retry = useCallback(() => { if (!loadable) { return; } loadable.reset(); forceUpdate({}); updateLoadable(loadable, forceUpdate); }, [loadable]); if (process.env.NODE_ENV !== 'production') { if (isBackend) { if (!loadable.done) { /* tslint:disable:next-line no-console */ console.error('react-imported-component: using not resolved loadable. You should `await whenComponentsReady()`.'); } } } return useMemo(() => ({ loadable, retry, update: forceUpdate, }), [loadable, retry, forceUpdate]); } export function useImported(importer, exportPicker = es6import, options = {}) { const topLoadable = getLoadable(importer); const { loadable, retry } = useLoadable(topLoadable, options); const { error, done, payload } = loadable; const loading = loadable.isLoading(); return useMemo(() => { if (error) { return { error, loadable, retry, }; } if (done) { return { imported: exportPicker(payload), loadable, retry, }; } return { loading, loadable, retry, }; }, [error, loading, payload, loadable]); } /** * A mix of React.lazy and useImported - uses React.lazy for Component and `useImported` to track the promise * not "retry"-able * @see if you need precise control consider {@link useImported} * @example * const Component = useLazy(() => import('./MyComponent'); * return <Component /> // throws to SuspenseBoundary if not ready */ export function useLazy(importer) { const [{ resolve, reject, lazyComponent }] = useState(() => { /* tslint:disable no-shadowed-variable */ let resolve; let reject; const promise = new Promise((rs, rej) => { resolve = rs; reject = rej; }); return { resolve, reject, lazyComponent: lazy(() => promise), }; /* tslint:enable */ }); const { error, imported } = useImported(importer); useEffect(() => { if (error) { reject(error); } if (imported) { resolve(imported); } }, [error, imported]); return lazyComponent; }