Some Relevant Context (I think)
I’ve worked on many projects that use what I believe are called “barrel dependencies” – a single file that exports a bunch of components for easy importing which looks like this:
// src/components/flexibleBlocks/index.js
export { default as Embed } from './Embed/Embed';
export { default as Image } from './Image/Image';
export { default as Text } from './Text/Text';
export { default as Video } from './Video/Video';
It means instead of writing:
import Embed from 'src/components/flexibleBlocks/Embed/Embed';
import Image from 'src/components/flexibleBlocks/Image/Image';
import Text from 'src/components/flexibleBlocks/Text/Text';
import Video from 'src/components/flexibleBlocks/Video/Video';
I can instead just do:
import { Embed, Image, Text, Video } from 'src/components/flexibleBlocks';
All well and good, works fine, no issues.
My App Structure
My apps and sites are often displaying content generated in a CMS, where the structure of a page is defined by the content editor, and cannot be hard-coded.
I use the following component to loop through the content:
// src/components/resolvers/FlexibleContentLoop.js
import { FlexibleContentResolver } from 'src/components/resolvers';
const FlexibleContentLoop = ({ flexible_content }) => {
if (flexible_content) {
return (
<>
{flexible_content.map((props, i) => (
<FlexibleContentResolver
key={i}
{...props}
/>
))}
</>
);
}
};
export default FlexibleContentLoop;
And the following to render the correct component for each entry in the previous loop:
// src/components/resolvers/FlexibleContentResolver.js
import * as blocks from 'src/components/flexibleBlocks';
import { Missing } from 'src/components/partials';
const FlexibleContentResolver = (props) => {
const { componentName } = props;
let Component;
if (componentName in blocks) {
Component = blocks[componentName];
return <Component {...props} />;
} else {
return <Missing>Missing block: {componentName}</Missing>;
}
};
export default FlexibleContentResolver;
It seems like an elegant solution to flexible CMS content, and I’ve used this for years with tools such as Create React App, Next.js, Gatsby, etc. without any issues.
The Problem:
Storybook doesn’t seem to like this. I think it boils down to circular dependencies.
I have my components setup so that you can nest one component loop inside another, which looks like this:
// src/components/flexibleBlocks/DynamicBlock/DynamicBlock.js
import { FlexibleContentLoop } from 'src/components/resolvers';
const DynamicBlock = ({ layouts }) => {
return (
<div>{layouts && <FlexibleContentLoop flexible_content={layouts} content_type="layout" />}</div>
);
};
export default DynamicBlock;
I can see why it would be a circular dependency, but it works, and allowing a component to be nested inside itself is a valid pattern.
In some cases I’m seeing this error:
ReferenceError: Cannot access '__WEBPACK_DEFAULT_EXPORT__' before initialization
at Module.default (http://localhost:6006/src_components_resolvers_index_js.iframe.bundle.js:113:42)
at Module.AgeScreen (http://localhost:6006/src_components_resolvers_index_js.iframe.bundle.js:773:111)
at registerExportsForReactRefresh (http://localhost:6006/vendors-node_modules_pmmmwh_react-refresh-webpack-plugin_client_ErrorOverlayEntry_js-node_mod-dafa79.iframe.bundle.js:796:36)
at Object.executeRuntime (http://localhost:6006/vendors-node_modules_pmmmwh_react-refresh-webpack-plugin_client_ErrorOverlayEntry_js-node_mod-dafa79.iframe.bundle.js:828:3)
at $ReactRefreshModuleRuntime$ (http://localhost:6006/src_components_resolvers_index_js.iframe.bundle.js:806:34)
at ./src/components/flexibleBlocks/index.js (http://localhost:6006/src_components_resolvers_index_js.iframe.bundle.js:819:2)
at options.factory (http://localhost:6006/runtime~main.iframe.bundle.js:642:31)
at __webpack_require__ (http://localhost:6006/runtime~main.iframe.bundle.js:28:33)
at fn (http://localhost:6006/runtime~main.iframe.bundle.js:299:21)
at ./src/components/resolvers/FlexibleContentResolver.js (http://localhost:6006/src_components_resolvers_index_js.iframe.bundle.js:2594:87)
And in others this error:
TypeError
Cannot read properties of undefined (reading 'default')
Call Stack
Module.FlexibleContentResolver
src_components_resolvers_index_js.iframe.bundle.js:2808:129
undefined
src_components_resolvers_index_js.iframe.bundle.js:2519:182
FlexibleContentLoop
src_components_resolvers_index_js.iframe.bundle.js:2519:34
renderWithHooks
vendors-node_modules_pmmmwh_react-refresh-webpack-plugin_client_ErrorOverlayEntry_js-node_mod-dafa79.iframe.bundle.js:58411:18
mountIndeterminateComponent
vendors-node_modules_pmmmwh_react-refresh-webpack-plugin_client_ErrorOverlayEntry_js-node_mod-dafa79.iframe.bundle.js:62175:13
beginWork
vendors-node_modules_pmmmwh_react-refresh-webpack-plugin_client_ErrorOverlayEntry_js-node_mod-dafa79.iframe.bundle.js:63688:16
HTMLUnknownElement.callCallback
vendors-node_modules_pmmmwh_react-refresh-webpack-plugin_client_ErrorOverlayEntry_js-node_mod-dafa79.iframe.bundle.js:46270:14
Object.invokeGuardedCallbackDev
vendors-node_modules_pmmmwh_react-refresh-webpack-plugin_client_ErrorOverlayEntry_js-node_mod-dafa79.iframe.bundle.js:46319:16
invokeGuardedCallback
vendors-node_modules_pmmmwh_react-refresh-webpack-plugin_client_ErrorOverlayEntry_js-node_mod-dafa79.iframe.bundle.js:46383:31
Is there a workaround to get storybook working with my flexible block components?
I don’t think there’s an alternative solution for my use case of CMS-driven nested flexible blocks.
Given that every other React tool I’ve used works fine with this pattern, I’m hoping there’s a way to get Storybook working too.
Thanks for any help.