Render dynamic Vue 3 slot content in StorybookJS

Viewed 43

Using the example Storybook code at the bottom of this post, I expect to see a Primary button rendered containing the text, "Primary Button", but instead the button renders following code, verbatim:

(...args) => {
        // If a user calls a compiled slot inside a template expression (#1745), it
        // can mess up block tracking, so by default we disable block tracking and
        // force bail out when invoking a compiled slot (indicated by the ._d flag).
        // This isn't necessary if rendering a compiled `<slot>`, so we flip the
        // ._d flag off when invoking the wrapped fn inside `renderSlot`.
        if (renderFnWithContext._d) {
            setBlockTracking(-1);
        }
        const prevInstance = setCurrentRenderingInstance(ctx);
        const res = fn(...args);
        setCurrentRenderingInstance(prevInstance);
        if (renderFnWithContext._d) {
            setBlockTracking(1);
        }
        if (true) {
            devtoolsComponentUpdated(ctx);
        }
        return res;
    }

How do I render Vue component slot content in Storybook?

The Storybook code:

// ./components/button/Button.stories.js
import AtxButton from './Button';
import ButtonTypes from './Button.types';


export default {
  /*  The title prop is optional.
  * See https://storybook.js.org/docs/vue/configure/overview#configure-story-loading
  * to learn how to generate automatic titles
  */
  title: 'Button',
  component: AtxButton,
  argTypes: {
    default: {
      description: 'The default Vue slot',
      control: 'text'
    }
  }
};

// We create a “template” of how args map to rendering
const Template = (args, { argTypes }) => ({
  props: Object.keys(argTypes),
  components: { AtxButton },
  setup() {
    // The args will now be passed down to the template
    return { args };
  },
  template: `
    <AtxButton v-bind="args">{{ args.default }}
    </AtxButton>
    `
});

// Each story then reuses that template
export const Primary = Template.bind({});
Primary.args = {
  intensity: ButtonTypes.Intensity.Primary,
  default: 'Primary Button'
};

export const Secondary = Template.bind({});
Secondary.args = {
  intensity: ButtonTypes.Intensity.Secondary,
  default: 'Secondary Button'
};

1 Answers

I had syntax error in my component code. The Button component is created using a render function, which includes slot content by accessing this.slots.default(). I was getting a console error claiming "this.slots.default() is not a function", so I changed it to this.slots.default, assuming it might have been a getter.

That wasn't the right solution, of course. I changed it back to this.slots.default(), and then ensured that slot content was always included in the Storybook story. Now it works perfectly!

Related