Dynamically updating args in Storybook from a Vue component

A straightforward way of using events from inside a Vue component to update the Storybook story's args.

Posted 17 January 2022   |   Tags Development Canvas   |   Back to blog

Introduction

Over the past few weeks I’ve been updating We Make Websites’ internal framework with Storybook so we had a single place to see a projects’ components in a sandboxed view.

While doing this I found it particularly tricky to dynamically update the args passed to the Vue component from an event in the Vue component itself. The component in question was a quantity selector with buttons to increment and decrement a value displayed in its input.

When I researched it there didn’t seem to be a straightforward solution, the Storybook controls available to update the arg worked but I thought this wasn’t the best way to simulate the different button states based on the quantity value.

By piecing together various GitHub issues and forum posts I built the below solution. It’s worth noting I’m not a Storybook expert so this could have some unforeseen consequence but it works in my testing with Vue 3.

Solution

The solution revolves around the updateArgs() function available in the useArgs() function which is only available as a decorator. The trouble with it being a decorator is that you can’t directly access it within a story so first we have to make it available.

As noted in the decorators documentation you can use the .storybook/preview.js file to globally set decorators. Make sure any component-level decorators don’t override it.

1import { useArgs } from '@storybook/client-api'
2
3export const decorators = [
4  (story, context) => {
5    const [_, updateArgs] = useArgs()
6    return story({ ...context, updateArgs })
7  },
8]

The above export makes the updateArgs() function globally available. The export is an array which allows you to add further customisations such as globally setting the template:

1export const decorators = [
2  (story, context) => {
3    const [_, updateArgs] = useArgs()
4    return story({ ...context, updateArgs })
5  },
6  () => ({ template: '<story />' }),
7]

Now in your story template you can access the updateArgs() function as below:

 1const Template = (args, { updateArgs }) => ({
 2  components: { QuantitySelector },
 3  setup() {
 4    return { args }
 5  },
 6  template: '<quantity-selector v-bind="args" @update-quantity="handleQuantityUpdate" />',
 7  methods: {
 8    handleQuantityUpdate(quantity) {
 9      updateArgs({ ...args, quantity })
10    },
11  },
12})

The <quantity-selector> component already emits the update-quantity custom event when the buttons are clicked (see custom events documentation). This custom event is used in the template to trigger a custom method which uses the updateArgs function.

Passing the existing args variable as a destructured object inside a new object with the new quantity variable is a shorthand way to update only the quantity variable’s value. If you passed just the quantity variable then you would lose all your other args.

References

Previous article Building performant colour swatches