facebook/react · error · Error
306
306
Error message
Element type is invalid. Received a promise that resolves to: ${loggedComponent}. Lazy element type must resolve to a class or function.${hint} What it means
mountIndeterminateComponent() re-dispatches a fiber after a React.lazy() promise settles. The resolved value must be a function component, class, forwardRef, memo, or context; anything else falls through to this throw, which prints what the promise actually resolved to. The dominant cause is a dynamic import whose module does not export a component as its default export.
Source
Thrown at packages/react-reconciler/src/ReactFiberBeginWork.js:2203
let hint = '';
if (__DEV__) {
if (
// $FlowFixMe[invalid-compare]
Component !== null &&
typeof Component === 'object' &&
// $FlowFixMe[invalid-compare]
Component.$$typeof === REACT_LAZY_TYPE
) {
hint = ' Did you wrap a component in React.lazy() more than once?';
}
}
const loggedComponent = getComponentNameFromType(Component) || Component;
// This message intentionally doesn't mention ForwardRef or MemoComponent
// because the fact that it's a separate type of work is an
// implementation detail.
throw new Error(
`Element type is invalid. Received a promise that resolves to: ${loggedComponent}. ` +
`Lazy element type must resolve to a class or function.${hint}`,
);
}
function mountIncompleteClassComponent(
_current: null | Fiber,
workInProgress: Fiber,
Component: any,
nextProps: any,
renderLanes: Lanes,
) {
resetSuspendedCurrentOnMountInLegacyMode(_current, workInProgress);
// Promote the fiber to a class and try rendering again.
workInProgress.tag = ClassComponent;
// The rest of this function is a fork of `updateClassComponent`View on GitHub (pinned to eafeac097b)
Solutions
- Open the module named in the error and make its default export a function or class component
- For named-only exports, map it: lazy(() => import('./Mod').then(m => ({default: m.NamedComp})))
- Remove nested lazy() wrappers - pass the import() directly to React.lazy
- Fix test mocks to resolve to {default: () => <div />}
Example fix
// before - Mod has no default export
const Panel = lazy(() => import('./Mod'));
// after - add a default export
export default function Mod() { /* ... */ }
// or map a named export
const Panel = lazy(() => import('./Mod').then(m => ({default: m.NamedPanel}))); Defensive patterns
Strategy: type-guard
Validate before calling
const loadPanel = async () => {
const mod = await import('./Mod');
if (!isComponentType(mod.default)) {
throw new Error(`./Mod default export is ${typeof mod.default}, expected a component`);
}
return {default: mod.default};
};
const Panel = lazy(loadPanel); Type guard
function isComponentType(x) {
if (typeof x === 'function') return true;
return typeof x === 'object' && x !== null && typeof x.$$typeof === 'symbol';
} Try / catch
Wrap lazy components in an ErrorBoundary with a fallback UI; reset the boundary on route change so a fixed deployment's chunk is loaded fresh instead of a cached rejected promise.
Prevention
- Default-export every lazy-loaded route or component
- Never wrap the output of React.lazy() in React.lazy() again
- Keep test mocks shaped as {default: () => <div />}
When it happens
Trigger: lazy(() => import('./Mod')) where './Mod' has only named exports; the default export is a plain object, constant, or hook; re-exporting one lazy component through another lazy() (React appends the hint 'Did you wrap a component in React.lazy() more than once?'); passing a non-component module (JSON, CSS) to lazy().
Common situations: Refactoring to named exports and forgetting the lazy wrapper; barrel files re-exporting through React.lazy; default-exporting a config object where a component was expected; test mocks resolving to {default: {}}; bundler default-export interop changes.
Related errors
AI-assisted analysis of facebook/react@eafeac097b (2026-08-21).
Data as JSON: /api/errors/abf95bb593a9bba7.
Report an issue: GitHub.