How To Add A Mock Component Factory To Storybook

Create a mock component factory in Sitecore Next.js JSS

April 30, 2022

By John Flores

If you are having issues with composite components with placeholder in Storybook or jest, this blog is for you.

Background

In Sitecore Next.js, componentFactory.js is a temp file generated at build time that maps Sitecore renderings to react to components. When you place a rendering into a placeholder, this factory is responsible for correctly finding the right react component to display. However, you may find that if you are trying to display a composite component (with its own custom placeholder) in Storybook, you will be faced with an error:

This blog is aimed at solving this issue by creating a mock component factory.

Component Missing Error Expandable Card

Component Factory Missing Error

The Issue

Storybook is simply unable to find the componentFactory.js because it is missing the Sitecore-specific wrapper that provides context and componentFactory.

My Setup

I have a parent component named ExpandableCardsContainer that contains a placeholder 'expandable-card' and a child component called ExpandableCard meant to be added to the 'expandable-card' placeholder.

  
    const ExpandableCardsContainer = ({ rendering }: ExpandableCardsContainerProps): JSX.Element => {
      return (
        <div className="expandable-cards-container">
          <Placeholder
            name={`expandable-card`}
            rendering={rendering}
          />
        </div>
      );
    };
  
  

    const ExpandableCard = ({ fields }: ExpandableCardProps): JSX.Element => (
      <div className="expandable-card">
        Content
      </div>
    );
  

My Storybook file:


    import React from 'react';
    import { ComponentStory, ComponentMeta } from '@storybook/react';
<span class="hljs-keyword"><span class="hljs-keyword"><span class="hljs-keyword">import</span></span></span> ExpandableCardsContainer <span class="hljs-keyword"><span class="hljs-keyword"><span class="hljs-keyword">from</span></span></span> <span class="hljs-string"><span class="hljs-string"><span class="hljs-string">'components/Feature/Page Content/ExpandableCardsContainer'</span></span></span>;
<span class="hljs-keyword"><span class="hljs-keyword"><span class="hljs-keyword">import</span></span></span> { loremIpsumGenerator } <span class="hljs-keyword"><span class="hljs-keyword"><span class="hljs-keyword">from</span></span></span> <span class="hljs-string"><span class="hljs-string"><span class="hljs-string">'lib/lorem-ipsum-generator'</span></span></span>;
<span class="hljs-keyword"><span class="hljs-keyword"><span class="hljs-keyword">import</span></span></span> { withDatasourceCheckComponentArgs } <span class="hljs-keyword"><span class="hljs-keyword"><span class="hljs-keyword">from</span></span></span> <span class="hljs-string"><span class="hljs-string"><span class="hljs-string">'src/stories/helper'</span></span></span>;

<span class="hljs-keyword"><span class="hljs-keyword"><span class="hljs-keyword">export</span></span></span> <span class="hljs-keyword"><span class="hljs-keyword"><span class="hljs-keyword">default</span></span></span> {
  title: <span class="hljs-string"><span class="hljs-string"><span class="hljs-string">'Feature/Page Content/ExpandableCardsContainer'</span></span></span>,
  component: ExpandableCardsContainer,
  argTypes: {},
} <span class="hljs-keyword"><span class="hljs-keyword"><span class="hljs-keyword">as</span></span></span> ComponentMeta&lt;<span class="hljs-keyword"><span class="hljs-keyword"><span class="hljs-keyword">typeof</span></span></span> ExpandableCardsContainer&gt;;

<span class="hljs-keyword"><span class="hljs-keyword"><span class="hljs-keyword">const</span></span></span> expandableCardsFactory = (count: <span class="hljs-built_in"><span class="hljs-built_in"><span class="hljs-built_in">number</span></span></span>): <span class="hljs-function"><span class="hljs-params"><span class="hljs-function"><span class="hljs-params"><span class="hljs-function"><span class="hljs-params"><span class="hljs-function"><span class="hljs-params"><span class="hljs-function"><span class="hljs-params"><span class="hljs-function"><span class="hljs-params">any</span></span></span></span></span></span></span></span></span></span></span><span class="hljs-function"><span class="hljs-function"><span class="hljs-function"> =&gt;</span></span></span></span> {
  <span class="hljs-keyword"><span class="hljs-keyword"><span class="hljs-keyword">return</span></span></span> <span class="hljs-built_in"><span class="hljs-built_in"><span class="hljs-built_in">Array</span></span></span>.from(<span class="hljs-built_in"><span class="hljs-built_in"><span class="hljs-built_in">Array</span></span></span>(count).keys()).map(<span class="hljs-function"><span class="hljs-function"><span class="hljs-function"><span class="hljs-function">(</span></span></span><span class="hljs-params"><span class="hljs-function"><span class="hljs-params"><span class="hljs-function"><span class="hljs-params"><span class="hljs-function"><span class="hljs-params"><span class="hljs-function"><span class="hljs-params"><span class="hljs-function"><span class="hljs-params">index: </span></span></span></span></span></span></span></span></span></span><span class="hljs-built_in"><span class="hljs-function"><span class="hljs-params"><span class="hljs-built_in"><span class="hljs-function"><span class="hljs-params"><span class="hljs-built_in"><span class="hljs-function"><span class="hljs-params"><span class="hljs-built_in"><span class="hljs-function"><span class="hljs-params"><span class="hljs-built_in"><span class="hljs-function"><span class="hljs-params"><span class="hljs-built_in">number</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span><span class="hljs-function"><span class="hljs-function"><span class="hljs-function">) =&gt;</span></span></span></span> {
    <span class="hljs-keyword"><span class="hljs-keyword"><span class="hljs-keyword">const</span></span></span> imgSrc = <span class="hljs-string"><span class="hljs-string"><span class="hljs-string"><span class="hljs-string">`stories/cards/cat</span></span></span><span class="hljs-subst"><span class="hljs-string"><span class="hljs-subst"><span class="hljs-string"><span class="hljs-subst"><span class="hljs-string"><span class="hljs-subst"><span class="hljs-string"><span class="hljs-subst"><span class="hljs-string"><span class="hljs-subst">${(index % </span></span></span></span></span></span></span></span></span></span><span class="hljs-number"><span class="hljs-string"><span class="hljs-subst"><span class="hljs-number"><span class="hljs-string"><span class="hljs-subst"><span class="hljs-number"><span class="hljs-string"><span class="hljs-subst"><span class="hljs-number"><span class="hljs-string"><span class="hljs-subst"><span class="hljs-number"><span class="hljs-string"><span class="hljs-subst"><span class="hljs-number">4</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span><span class="hljs-string"><span class="hljs-subst"><span class="hljs-string"><span class="hljs-subst"><span class="hljs-string"><span class="hljs-subst"><span class="hljs-string"><span class="hljs-subst"><span class="hljs-string"><span class="hljs-subst">) + </span></span></span></span></span></span></span></span></span></span><span class="hljs-number"><span class="hljs-string"><span class="hljs-subst"><span class="hljs-number"><span class="hljs-string"><span class="hljs-subst"><span class="hljs-number"><span class="hljs-string"><span class="hljs-subst"><span class="hljs-number"><span class="hljs-string"><span class="hljs-subst"><span class="hljs-number"><span class="hljs-string"><span class="hljs-subst"><span class="hljs-number">1</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span><span class="hljs-string"><span class="hljs-subst"><span class="hljs-string"><span class="hljs-subst"><span class="hljs-string"><span class="hljs-subst"><span class="hljs-string"><span class="hljs-subst"><span class="hljs-string"><span class="hljs-subst">}</span></span></span></span></span></span></span></span></span></span></span><span class="hljs-string"><span class="hljs-string"><span class="hljs-string">.jpg`</span></span></span></span>;
    <span class="hljs-keyword"><span class="hljs-keyword"><span class="hljs-keyword">return</span></span></span> {
      componentName: <span class="hljs-string"><span class="hljs-string"><span class="hljs-string">'ExpandableCard'</span></span></span>,
      dataSource: <span class="hljs-string"><span class="hljs-string"><span class="hljs-string">'Expandable Card Datasource'</span></span></span>,
      fields: {
        ... content
      },
    };
  });
};

<span class="hljs-keyword"><span class="hljs-keyword"><span class="hljs-keyword">const</span></span></span> Template: ComponentStory&lt;<span class="hljs-keyword"><span class="hljs-keyword"><span class="hljs-keyword">typeof</span></span></span> ExpandableCardsContainer&gt; = <span class="hljs-function"><span class="hljs-function"><span class="hljs-function"><span class="hljs-function">(</span></span></span><span class="hljs-params"><span class="hljs-function"><span class="hljs-params"><span class="hljs-function"><span class="hljs-params"><span class="hljs-function"><span class="hljs-params"><span class="hljs-function"><span class="hljs-params"><span class="hljs-function"><span class="hljs-params">args</span></span></span></span></span></span></span></span></span></span></span><span class="hljs-function"><span class="hljs-function"><span class="hljs-function">) =&gt;</span></span></span></span> (
  &lt;ExpandableCardsContainer {...args} /&gt;
);

<span class="hljs-keyword"><span class="hljs-keyword"><span class="hljs-keyword">export</span></span></span> <span class="hljs-keyword"><span class="hljs-keyword"><span class="hljs-keyword">const</span></span></span> TenCards = Template.bind({});
TenCards.args = {
  ...withDatasourceCheckComponentArgs,
  rendering: {
    componentName: <span class="hljs-string"><span class="hljs-string"><span class="hljs-string">'ExpandableCardsContainer'</span></span></span>,
    placeholders: {
      <span class="hljs-string"><span class="hljs-string"><span class="hljs-string">'expandable-card'</span></span></span>: expandableCardsFactory(<span class="hljs-number"><span class="hljs-number"><span class="hljs-number">10</span></span></span>),
    },
  },
};

The Solution

Add or merge code below to your Storybook's preview.js.


    const mockSitecoreContext = {
      context: {
        pageEditing: false,
      },
      setContext: () => { },
    };
<span class="hljs-keyword"><span class="hljs-keyword"><span class="hljs-keyword">export</span></span></span> <span class="hljs-keyword"><span class="hljs-keyword"><span class="hljs-keyword">const</span></span></span> mockComponentFactory = <span class="hljs-function"><span class="hljs-keyword"><span class="hljs-function"><span class="hljs-keyword"><span class="hljs-function"><span class="hljs-keyword"><span class="hljs-function"><span class="hljs-keyword"><span class="hljs-function"><span class="hljs-keyword"><span class="hljs-function"><span class="hljs-keyword">function</span></span></span></span></span></span></span></span></span></span></span><span class="hljs-function"><span class="hljs-function"><span class="hljs-function"> (</span></span></span><span class="hljs-params"><span class="hljs-function"><span class="hljs-params"><span class="hljs-function"><span class="hljs-params"><span class="hljs-function"><span class="hljs-params"><span class="hljs-function"><span class="hljs-params"><span class="hljs-function"><span class="hljs-params">componentName</span></span></span></span></span></span></span></span></span></span></span><span class="hljs-function"><span class="hljs-function"><span class="hljs-function">) </span></span></span></span>{
  <span class="hljs-keyword"><span class="hljs-keyword"><span class="hljs-keyword">const</span></span></span> components = <span class="hljs-keyword"><span class="hljs-keyword"><span class="hljs-keyword">new</span></span></span> <span class="hljs-built_in"><span class="hljs-built_in"><span class="hljs-built_in">Map</span></span></span>();
  components.set(<span class="hljs-string"><span class="hljs-string"><span class="hljs-string">'YOUR RENDERING NAME'</span></span></span>, &lt;YOUR COMPONENT&gt;)

  <span class="hljs-keyword"><span class="hljs-keyword"><span class="hljs-keyword">const</span></span></span> component = components.get(componentName);

  <span class="hljs-keyword"><span class="hljs-keyword"><span class="hljs-keyword">if</span></span></span> (component?.element) {
    <span class="hljs-keyword"><span class="hljs-keyword"><span class="hljs-keyword">return</span></span></span> component.element();
  }

  <span class="hljs-keyword"><span class="hljs-keyword"><span class="hljs-keyword">return</span></span></span> component?.default || component;
};

<span class="hljs-keyword"><span class="hljs-keyword"><span class="hljs-keyword">export</span></span></span> <span class="hljs-keyword"><span class="hljs-keyword"><span class="hljs-keyword">const</span></span></span> decorators = [
  <span class="hljs-function"><span class="hljs-function"><span class="hljs-function"><span class="hljs-function">(</span></span></span><span class="hljs-params"><span class="hljs-function"><span class="hljs-params"><span class="hljs-function"><span class="hljs-params"><span class="hljs-function"><span class="hljs-params"><span class="hljs-function"><span class="hljs-params"><span class="hljs-function"><span class="hljs-params">Story</span></span></span></span></span></span></span></span></span></span></span><span class="hljs-function"><span class="hljs-function"><span class="hljs-function">) =&gt;</span></span></span></span> (
    <span class="xml"><span class="hljs-tag"><span class="xml"><span class="hljs-tag"><span class="xml"><span class="hljs-tag"><span class="xml"><span class="hljs-tag"><span class="xml"><span class="hljs-tag"><span class="xml"><span class="hljs-tag">&lt;</span></span></span></span></span></span></span></span></span></span><span class="hljs-name"><span class="xml"><span class="hljs-tag"><span class="hljs-name"><span class="xml"><span class="hljs-tag"><span class="hljs-name"><span class="xml"><span class="hljs-tag"><span class="hljs-name"><span class="xml"><span class="hljs-tag"><span class="hljs-name"><span class="xml"><span class="hljs-tag"><span class="hljs-name">SitecoreContext</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span><span class="xml"><span class="hljs-tag"><span class="xml"><span class="hljs-tag"><span class="xml"><span class="hljs-tag"><span class="xml"><span class="hljs-tag"><span class="xml"><span class="hljs-tag"> </span></span></span></span></span></span></span></span></span></span><span class="hljs-attr"><span class="xml"><span class="hljs-tag"><span class="hljs-attr"><span class="xml"><span class="hljs-tag"><span class="hljs-attr"><span class="xml"><span class="hljs-tag"><span class="hljs-attr"><span class="xml"><span class="hljs-tag"><span class="hljs-attr"><span class="xml"><span class="hljs-tag"><span class="hljs-attr">context</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span><span class="xml"><span class="hljs-tag"><span class="xml"><span class="hljs-tag"><span class="xml"><span class="hljs-tag"><span class="xml"><span class="hljs-tag"><span class="xml"><span class="hljs-tag">=</span></span></span></span></span></span></span></span></span></span><span class="hljs-string"><span class="xml"><span class="hljs-tag"><span class="hljs-string"><span class="xml"><span class="hljs-tag"><span class="hljs-string"><span class="xml"><span class="hljs-tag"><span class="hljs-string"><span class="xml"><span class="hljs-tag"><span class="hljs-string"><span class="xml"><span class="hljs-tag"><span class="hljs-string">{mockSitecoreContext}</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span><span class="xml"><span class="hljs-tag"><span class="xml"><span class="hljs-tag"><span class="xml"><span class="hljs-tag"><span class="xml"><span class="hljs-tag"><span class="xml"><span class="hljs-tag"> </span></span></span></span></span></span></span></span></span></span><span class="hljs-attr"><span class="xml"><span class="hljs-tag"><span class="hljs-attr"><span class="xml"><span class="hljs-tag"><span class="hljs-attr"><span class="xml"><span class="hljs-tag"><span class="hljs-attr"><span class="xml"><span class="hljs-tag"><span class="hljs-attr"><span class="xml"><span class="hljs-tag"><span class="hljs-attr">componentFactory</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span><span class="xml"><span class="hljs-tag"><span class="xml"><span class="hljs-tag"><span class="xml"><span class="hljs-tag"><span class="xml"><span class="hljs-tag"><span class="xml"><span class="hljs-tag">=</span></span></span></span></span></span></span></span></span></span><span class="hljs-string"><span class="xml"><span class="hljs-tag"><span class="hljs-string"><span class="xml"><span class="hljs-tag"><span class="hljs-string"><span class="xml"><span class="hljs-tag"><span class="hljs-string"><span class="xml"><span class="hljs-tag"><span class="hljs-string"><span class="xml"><span class="hljs-tag"><span class="hljs-string">{mockComponentFactory}</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span><span class="xml"><span class="hljs-tag"><span class="xml"><span class="hljs-tag"><span class="xml"><span class="hljs-tag"><span class="xml"><span class="hljs-tag"><span class="xml"><span class="hljs-tag">&gt;</span></span></span></span></span></span></span></span></span></span></span><span class="xml"><span class="xml"><span class="xml">
      </span></span></span><span class="hljs-tag"><span class="xml"><span class="hljs-tag"><span class="xml"><span class="hljs-tag"><span class="xml"><span class="hljs-tag"><span class="xml"><span class="hljs-tag"><span class="xml"><span class="hljs-tag">&lt;</span></span></span></span></span></span></span></span></span></span><span class="hljs-name"><span class="xml"><span class="hljs-tag"><span class="hljs-name"><span class="xml"><span class="hljs-tag"><span class="hljs-name"><span class="xml"><span class="hljs-tag"><span class="hljs-name"><span class="xml"><span class="hljs-tag"><span class="hljs-name"><span class="xml"><span class="hljs-tag"><span class="hljs-name">Story</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span><span class="xml"><span class="hljs-tag"><span class="xml"><span class="hljs-tag"><span class="xml"><span class="hljs-tag"><span class="xml"><span class="hljs-tag"><span class="xml"><span class="hljs-tag"> /&gt;</span></span></span></span></span></span></span></span></span></span></span><span class="xml"><span class="xml"><span class="xml">
    </span></span></span><span class="hljs-tag"><span class="xml"><span class="hljs-tag"><span class="xml"><span class="hljs-tag"><span class="xml"><span class="hljs-tag"><span class="xml"><span class="hljs-tag"><span class="xml"><span class="hljs-tag">&lt;/</span></span></span></span></span></span></span></span></span></span><span class="hljs-name"><span class="xml"><span class="hljs-tag"><span class="hljs-name"><span class="xml"><span class="hljs-tag"><span class="hljs-name"><span class="xml"><span class="hljs-tag"><span class="hljs-name"><span class="xml"><span class="hljs-tag"><span class="hljs-name"><span class="xml"><span class="hljs-tag"><span class="hljs-name">SitecoreContext</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span><span class="xml"><span class="hljs-tag"><span class="xml"><span class="hljs-tag"><span class="xml"><span class="hljs-tag"><span class="xml"><span class="hljs-tag"><span class="xml"><span class="hljs-tag">&gt;</span></span></span></span></span></span></span></span></span></span></span></span>
  ),
];

Note that we straight up copied the structure of the auto-generated componentFactory.js, but we manually set the map from the rendering name to their respective components. In my case, II had to add 'components.set('ExpandableCard', ExpandableCard)'. Do not forget to import the component FYI.

Additionally, if you have not, I included the mockSitecoreContext in order to bypass 'missing sitecore Context' error.

Boom.

Working Component

John Headshot

John Flores

Front-End Developer

John is a Senior Front-End Developer who is passionate about design and development. Outside of work, John has wide range of hobbies, from his plant collection, being a dog daddy, and a foodie.

Second CTA Ogilvy's Legacy

Today, David Ogilvy's influence can still be felt in the world of advertising.

Ogilvy's Influence Example
Emphasis on research Market research is a crucial part of any successful advertising campaign
Focus on headlines A strong headline can make the difference between an ad that is noticed and one that is ignored
Use of visuals Compelling images and graphics are essential for capturing audience attention