react-imported-component
Version:
I will import your component, and help to handle it
132 lines (131 loc) • 4.21 kB
JavaScript
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;
}